cmake教程

目录

[1. 单个源文件](#1. 单个源文件)

[2. 同一目录下的多个源文件](#2. 同一目录下的多个源文件)

[3. 不同目录下的多个源文件](#3. 不同目录下的多个源文件)

[4. 正规的组织架构](#4. 正规的组织架构)

[4.1 写法一](#4.1 写法一)

[4.2 写法二](#4.2 写法二)

[5. 动态库和静态库的编译控制](#5. 动态库和静态库的编译控制)

[6. 对库进行链接](#6. 对库进行链接)

[7. 添加编译选项(-std=c++11 等等)](#7. 添加编译选项(-std=c++11 等等))

[8. 添加控制选项](#8. 添加控制选项)

[8.1 只生成部分 bin/库文件](#8.1 只生成部分 bin/库文件)

[8.2 只编译部分代码(宏控制)](#8.2 只编译部分代码(宏控制))


CMake 是开源、跨平台的构建工具,可以让我们不用去编写复杂的 Makefile,可以生成本地的Makefile,而且配置文件可以直接拿到其它平台上使用,无需修改,非常方便,是个非常强大并值得一学的工具。

bash 复制代码
sudo apt install cmake
cmake --version

1. 单个源文件

在 main.c 相同目录下编写 CMakeLists.txt

cpp 复制代码
cmake_minimum_required(VERSION 2.8)

project(demo)

add_executable(main main.c)
  • cmake_minimum_required :指定运行此配置文件所需的 CMake 的最低版本;
  • project(demo):表示项目的名称是 demo 。
  • add_executable : 将名为 main.c 的源文件编译成一个名称为main 的可执行文件

然后在 main.c 所在目录,执行

bash 复制代码
cmake .

生成了 Makefile 和一些 cmake 运行时自动生成的文件。然后继续 make

bash 复制代码
make

t想重新生成 main

bash 复制代码
make clean

2. 同一目录下的多个源文件

比如有 main.c 和 add.c

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c add.c)

如果源文件比较多,cmake 提供了一个命令可以把指定目录下所有的源文件存储在一个变量中

cpp 复制代码
aux_source_directory(<dir> <variable>)
  • dir:指定目录
  • var:用于存放源文件列表的变量

aux_source_directory 命令会查找指定目录下的所有源文件,然后将结果存进指定变量名

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})

aux_source_directory 把当前目录下的源文件存列表存放到变量 SRC_LIST 里,然后在 add_executable 里调用 SRC_LIST

但aux_source_directory()也存在弊端,它会把指定目录下的所有源文件都加进来,可能会加入一些我们不需要的文件,可以使用 set 命令去新建变量来存放需要的源文件

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

set( SRC_LIST
./main.c
./add.c
./sub.c)

add_executable(main ${SRC_LIST})

3. 不同目录下的多个源文件

add.c 和 add.h 放到 add 目录下,sub.c 和 sub.h 则放到 sub 目录下

CMakeLists.txt 和 main.c 在同一目录下

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

include_directories (add sub)

aux_source_directory (add SRC_ADD_LIST)
aux_source_directory (sub SRC_SUB_LIST)

add_executable (main main.c ${SRC_ADD_LIST} ${SRC_SUB_LIST})

include_directories:向工程添加多个指定头文件的搜索路径,路径之间用空格分隔

也可以在 main.c 里指定路径

cpp 复制代码
#include "add/add.h"
#include "sub/sub.h"

4. 正规的组织架构

  • 源文件放到 src 目录下
  • 头文件放入到 include 文件下
  • 生成的对象文件放入到 build 目录下
  • 最终输出的 elf 文件会放到 bin 目录下
  • 生成的库文件存放到 lib 目录下

这样整个结构更加清晰。

4.1 写法一

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

add_subdirectory (src)

add_subdirectory():向当前工程添加存放源文件的子目录,还可以指定中间二进制和目标二进制的存放位置

这里指定 src 目录下存放了源文件,当执行 cmake 时,就会进入 src 目录下去找 CMakeLists.txt,所以在 src 目录下也建立一个CMakeLists.txt:

cpp 复制代码
aux_source_directory (. SRC_LIST)

include_directories (../include)

add_executable (main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

set:用于定义变量的

EXECUTABLE_OUT_PATH 和 PROJECT_SOURCE_DIR 是 CMake 的预定义变量

  • EXECUTABLE_OUTPUT_PATH:目标二进制可执行文件的存放位置
  • PROJECT_SOURCE_DIR:工程的根目录

这里 set 的意思是把存放 elf 文件的位置设置为工程根目录下的 bin 目录。

先切到 build 目录下,然后运行 cmake:

cpp 复制代码
cmake ..

Makefile 会在 build 目录下生成,然后在 build 目录下运行 make

cpp 复制代码
make

再切到 bin 目录下,发现 main 生成

为什么在build目录下运行cmake?

cmake 运行时生成的附带文件就会跟源码文件混在一起,这样会对程序的目录结构造成污染,而在 build 目录下运行 cmake,生成的附带文件就只会待在 build 目录下,如果我们不想要这些文件了就可以直接清空 build 目录,非常方便。

4.2 写法二

前面使用了两个 CMakeLists.txt,最外层的 CMakeLists.txt 用于掌控全局,使用 add_subdirectory来控制其它目录下的 CMakeLists.txt 的运行。

也可以只使用一个 CMakeLists.txt,把最外层的 CMakeLists.txt 内容改成:

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

aux_source_directory (src SRC_LIST)

include_directories (include)

add_executable (main ${SRC_LIST})

把 src 目录下的 CMakeLists.txt 删掉,build 目录下执行 cmake,然后执行 make

cpp 复制代码
cmake ..
make

5. 动态库和静态库的编译控制

有时只需要编译出动态库和静态库,然后等着让其它程序去使用。

在 build 目录下运行 cmake,并把生成的库文件存放到 lib 目录下,CMakeLists.txt 内容如下:

cpp 复制代码
cmake_minimum_required (VERSION 3.5)

project (demo)

aux_source_directory (src SRC_LIST)

add_library (mym_shared SHARED ${SRC_LIST})
add_library (mym_static STATIC ${SRC_LIST})

set_target_properties (mym_shared PROPERTIES OUTPUT_NAME "mym")
set_target_properties (mym_static PROPERTIES OUTPUT_NAME "mym")

set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
  • add_library:生成动态库或静态库

(第一个参数指定库的名字;第二个参数决定是动态还是静态,如果没有就默认静态;第三个参数指定生成库的源文件)

  • set_target_properties:设置最终生成的库的名称,还有其它功能,如设置库的版本号等等
  • LIBRARY_OUTPUT_PATH:库文件的默认输出路径,这里设置为工程目录下的 lib 目录

set_target_properties 重新定义了库的输出名称,如果不使用 set_target_properties 也可以,那么库的名称就是 add_library 里定义的名称,只是连续两次使用 add_library 指定库名称时(第一个参数),这个名称不能相同,而 set_target_properties 可以把名称设置为相同,只是最终生成的库文件后缀不同(一个是.so,一个是.a)

然后进入 build 目录下运行 cmake

cpp 复制代码
cmake ..
make

到 lib 目录下进行查看,生成了动态库和静态库

6. 对库进行链接

已经生成了库,那么就要进行链接

cpp 复制代码
nimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set (SRC_LIST ${PROJECT_SOURCE_DIR}/src/main.c)

# find add.h sub.h
include_directories (${PROJECT_SOURCE_DIR}/zytm/include)

find_library(ZYTM_LIB mym HINTS ${PROJECT_SOURCE_DIR}/zytm/lib)

add_executable (main ${SRC_LIST})

target_link_libraries (main ${ZYTM_LIB})
  • find_library:在指定目录下查找指定库,并把库的绝对路径存放到变量里

(第一个参数是变量名称,第二个参数是库名称,第三个参数是HINTS,第四个参数是路径)

  • target_link_libraries:把目标文件与库文件进行链接

使用 find_library 的好处是在执行 cmake 时就会去查找库是否存在,可以提前发现错误,不用等到链接时

在 lib 目录下有 mym 的静态库和动态库,find_library(ZYTM_LIB mym ...) 默认是查找动态库,如果想直接指定使用动态库还是静态库,可以写成 find_library(ZYTM_LIB libmym.so ...) 或者find_library(ZYTM_LIB libtmym.a ...)

查看 elf 文件使用了哪些库,可以使用 readelf -d ./xx 来查看。

之前库查找方法是 link_directories,但是很多用户反映运行时有问题,推荐使用 find_library 或者 find_package

然后进入 build 目录下,运行 cmake

cpp 复制代码
cmake ..
make

最后进入到 bin 目录下查看,发现 main 已经生成,并能成功运行

7. 添加编译选项(-std=c++11 等等)

如果编译程序时想添加一些编译选项,如‑Wall,‑std=c++11 等,可以使用 add_compile_options 来进行操作

cpp 复制代码
cmake_minimum_required (VERSION 2.8)

project (demo)

add_compile_options(-std=c++11 -Wall)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

aux_source_directory(./src SRC_LIST)

add_executable(main ${SRC_LIST})

然后进入 build 目录,执行 cmake,就可以在 bin 目录下得到 main 的 elf 文件

bash 复制代码
cmake .. && make

8. 添加控制选项

如果想在编译代码时只编译一些指定的源码,可以使用 cmake 的 option 命令,主要遇到的情况分为两种:

  1. 本来要生成多个 bin 或 库文件,现在只想生成部分指定的 bin 或 库文件
  2. 对于同一个 bin 文件,只想编译其中部分代码(使用宏来控制)

8.1 只生成部分 bin/库文件

第一种情况,假设我们现在的工程会生成两个bin文件,main1 和 main2,外层 CMakeLists.txt 的内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.5)

project(demo)

option(MYDEBUG "enable debug compilation" OFF)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(src)

option:

  • 第一个参数是这个 option 的名字;
  • 第二个参数是字符串,用来描述这个option是来干嘛的;
  • 第三个是 option 的值,ON 或 OFF,也可以不写,不写就是默认 OFF

然后编写 src 目录下的 CMakeLists.txt:

cpp 复制代码
cmake_minimum_required (VERSION 3.5)

add_executable(main1 main1.cpp)

if (MYDEBUG)
add_executable(main2 main2.cpp)
else()
message(STATUS "Currently is not in debug mode") 
endif()

这里使用了 if‑else 来根据 option 来决定是否编译 main2.cpp

然后进入 build 目录下执行 cmake .. && make 就可以只编译出 main1,如果想编译出 main2,就把 MYDEBUG 设置为 ON,再次输入 cmake .. && make 重新编译。

每次想改变 MYDEBUG 时都需要去修改 CMakeLists.txt,有点麻烦,其实可以通过 cmake 的命令行去操作,例如我们想把 MYDEBUG 设置为 ON,先 cd 到 build 目录,然后输入

bash 复制代码
cmake .. -DMYDEBUG=ON

这样就可以编译出 main1 和 main2(在 bin 目录下)

8.2 只编译部分代码(宏控制)

假设我们有个 main.c

cpp 复制代码
#include <stdio.h>
int main(void)
{
    #ifdef WWW1
    printf("hello world1\n");
    #endif

    #ifdef WWW2 
    printf("hello world2\n");
    #endif

    return 0;
}

可以通过定义宏来控制打印的信息

cpp 复制代码
cmake_minimum_required(VERSION 3.5)

project(demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

option(WWW1 "print one message" OFF)
option(WWW2 "print another message" OFF)

if (WWW1)
add_definitions(-DWWW1)
endif()

if (WWW2)
add_definitions(-DWWW2)
endif()

add_executable(main main.c)

这里把 option 的名字保持和 main.c 里的宏名称一致,更直观一点

通过与 add_definitions() 的配合,就可以控制单个 bin 文件的打印输出了

然后进入 build 目录下执行cmake .. && make,最后到 bin 目录下执行./main,可以看到打印为空

接着分别按照下面指令去执行,然后查看打印效果:

bash 复制代码
cmake .. -DWWW1=ON -DWWW2=OFF && make
cmake .. -DWWW1=OFF -DWWW2=ON && make
cmake .. -DWWW1=ON -DWWW2=ON && make

**注意:**假设有两个 options 叫 A 和 B,先调用 cmake 设置了 A,下次再调用 cmake 去设置 B,如果没有删除上次执行 cmake 时产生的缓存文件,那么这次虽然没设置 A,也会默认使用 A 上次的 option 值。所以如果 option 有变化,要么删除上次执行 cmake 时产生的缓存文件,要么把所有的 option 都显式的指定其值。

相关推荐
安大小万1 小时前
蓝桥杯Dev-C++ 5.11(编译器)使用细节排坑
c++
raindayinrain2 小时前
c++并发--原子操作,内存顺序,屏障
c++·原子操作·操作顺序
张小姐的猫2 小时前
【AI大模型接入SDK】 —— SQLite上手
linux·开发语言·c++·人工智能·python·log4j
hansang_IR2 小时前
【题解】[JSOI2016] 最佳团体(分数规划)
c++·算法
代码中介商2 小时前
C++ 预约系统实战(二):TCP + JSON 自定义协议设计
c++·网络协议
渡我白衣2 小时前
Util工具类功能设计与类设计
linux·服务器·网络·c++·人工智能·目标检测·机器学习
小王的创意工坊2 小时前
C++ 在线判题平台(OJ)测试报告
c++·可用性测试
sdm0704273 小时前
仿muduo库实现高并发服务器-下
开发语言·网络·c++·多路转接
爱和冰阔落3 小时前
【Linux】手写日志与固定线程池:任务队列、工作线程和安全退出
linux·运维·c++·redis·安卓