前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习了C/C++,居然不了解Cmake这一利器

学习了C/C++,居然不了解Cmake这一利器

作者头像
鳄鱼儿
发布2024-05-29 12:05:55
1190
发布2024-05-29 12:05:55
举报

CMake 是一个跨平台的自动化建构系统,可以用简单的命令来控制软件编译过程。下面是一个关于如何使用 CMake 进行项目配置和编译的教程。

一、基础配置

1、设置CMake 版本要求

因为 Cmake 版本之间存在差异,在编写 CMakefile 时还需要用 cmake_minimum_required 语句设置一个最低版本要求,一般位于文件第一行。

格式如下:

代码语言:javascript
复制
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
  • VERSION min:CMake 最小版本
  • <policy_max>:CMake 最高版本,在 3.12 版本中引入,如果 cmake 是_3.12_之前的版本,会被忽略。
  • FATAL_ERROR: 该参数在 cmake 的_2.6_及以后的版本被忽略,在 cmake 的_2.4_及以前的版本,需要指明该参数,使得 cmake 能提示失败而不是一个警告。

如果 CMake 运行的版本低于<min>要求的版本,它将停止处理 project 并报告错误。

2、项目版本规定

项目中通常需要版本号,方便后期进行管理,在 CMakeLists.txt 文件中添加以下代码,用来设置项目的版本号并生成 version.h 文件

可以通过 project 命令进行配置:

代码语言:javascript
复制
project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

<> 是必填的,[] 是可选的。实例如下:

代码语言:javascript
复制
project(CMakeTemplate VERSION 1.0.0 LANGUAGES C CXX)
  • CMakeTemplate:项目名称
  • VERSION 1.0.0:通过 VERSION 指定版本号,版本号格式为 major.minor.patch.tweak
    • major(主版本号)
    • minor(次版本号)
    • patch(补丁版本号)
    • tweak
  • LANGUAGES:可选,如果未配置,默认使用 C 以及 CXX

并且CMake会将对应的值分别赋值给对应的变量(如果没有设置,则为空字符串)

名称

变量名

major(主版本号)

PROJECT_VERSION_MAJOR

1

minor(次版本号)

PROJECT_VERSION_MINOR

0

patch(补丁版本号)

PROJECT_VERSION_PATCH

0

tweak

PROJECT_VERSION_TWEAK

VERSION

CMAKE_PROJECT_NAME

1.0.0

也可以使用一个 version.h 文件指定项目版本。

我们可以通过宏定义设置一个 version.h.in 文件:

代码语言:javascript
复制
#define VERSION_MAJOR @CMakeTemplate_VERSION_MAJOR@
#define VERSION_MINOR @CMakeTemplate_VERSION_MINOR@
#define VERSION_PATCH @CMakeTemplate_VERSION_PATCH@

并设置如下的CMake 文件。

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.12...3.19.1)
project(CMakeTemplate VERSION 1.0.0 LANGUAGES C CXX)

configure_file(
    ${CMAKE_SOURCE_DIR}/version.h.in
    ${CMAKE_BINARY_DIR}/version.h
)

在执行 cmake 构建后,会自动生成 version.h 文件,得到 version.h 文件如下:

代码语言:javascript
复制
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define VERSION_PATCH 0

3、配置编译选项

设定编译时语言版本,可以通过设置 CMake 编译器标志来指定项目所使用的编程语言版本,例如:

代码语言:javascript
复制
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 99)

声明了C使用 c99 标准,C++使用 c++11 标准。

如此声明是为了项目在不同的机器上编译时使用统一语言版本。

可以设置编译器的选项,例如优化级别、警告选项等,例如:

代码语言:javascript
复制
add_compile_options(-Wall -Wextra -pedantic -Werror)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")
  • add_compile_options:添加了一些额外的警告信息选项(-Wall-Wextra-pedantic)和将警告视为错误的选项(-Werror)。
  • CMAKE_C_FLAGS: 为C代码添加了-pipe标志,并将C标准设置为C99。
  • CMAKE_CXX_FLAGS: 为C++代码添加了-pipe标志,并将C++标准设置为C++11。

这些命令有助于执行编码标准并在编译过程中发现潜在问题。

4、配置编译类型

可以配置 CMake 编译器类型,例如 DebugReleaseRelWithDebInfo MinSizeRel 等,例如:

代码语言:javascript
复制
set(CMAKE_BUILD_TYPE Debug)

也可不在cmake 文件中指定,而是通过执行cmake 时通过 -B 指令参数指定,例如:

代码语言:javascript
复制
cmake -B build -DCMAKE_BUILD_TYPR=Debug
  • -B build:指定构建目录,-B 选项后面跟着的是构建目录的路径,会在当前工作目录下创建(如果不存在的话)并使用这个目录来存放生成的构建系统文件。
  • -DCMAKE_BUILD_TYPE=Debug:设置了构建类型。-D 选项用于定义变量,这里定义了 CMAKE_BUILD_TYPE 变量,其值被设置为 Debug,生成调试版本的构建文件,通常包括额外的调试信息,以便于我们去调试程序。

5、添加全局宏定义

可以添加全局的宏定义,使用 add_definitions 可以增加全局的宏定义,这样在源码中可以判断宏定义实现不同的代码逻辑。

代码语言:javascript
复制
add_definitions(-DENABLE_FEATURE_X -ISDEBUG)

6、添加 include 目录

源代码中包含多个头文件,可以通过 include_directories 添加头文件所在的 include 目录,这个命令会将指定的目录添加到编译器的头文件搜索路径中,使得在编译源代码时,编译器能够找到这些目录下的头文件。

代码语言:javascript
复制
include_directories(src/inc)

从 CMake 3.0 开始,推荐使用 target_include_directories 命令代替 include_directories

  • target_include_directories 允许指定特定目标(可执行文件或库)的头文件搜索路径,这提供了更高的灵活性和更清晰的代码组织。
    • target_include_directories(my_target PRIVATE ${PROJECT_SOURCE_DIR}/include)
    • my_target 是想要添加头文件搜索路径的目标,PRIVATE 表示这些头文件目录仅用于编译 my_target,而不传递给链接 my_target 的其他目标。

target_include_directories 命令这种方式使得构建配置更加模块化和清晰。

二、编译目标文件——示例演示

小鱼以一个cmake 模板示例一个CMake Project的模板仓库来细说。

编写cmake 需要确认编译目标需要的源文件,以及链接需要依赖的库。

  • 编译目标:静态库、动态库、可执行文件
Pasted image 20240527104305.png
Pasted image 20240527104305.png

这里我们需要做的有以下任务:

  • 把 math 路径下编译成静态库;
  • main.c 编译成可执行文件,并依赖math 静态库;
  • 将 test 路径下的测试源文件编译成执行文件,并使用命令进行测试。

1、编译静态库

首先,我们需要将 src/c/math 路径下源文件编译成静态库。先使用 file 或者 set 命令获取源文件路径下的文件列表,再通过 add_library 命令来编译静态库。

代码语言:javascript
复制
file(GLOB_RECURSE MATH_LIB_SRC
		src/c/math/*.c
		)
add_library(math STATIC ${MATH_LIB_SRC})
  • file:用于递归地查找与指定模式匹配的文件。
  • add_library:用于定义一个库目标,这里定义了一个名为 math 的库,STATIC 表示静态库,动态库可使用 SHARED

递归地查找 src/c/math/ 目录及其子目录下所有的 .c 文件,并将这些文件的路径存储在 MATH_LIB_SRC 变量中。并使用这些 .c 文件作为源文件,创建一个名为 math 的静态库。

2、编译可执行文件

可以通过 add_executable 命令来编译可执行文件,首先我们先定位源文件,这里使用 add_executable 命令。若存在依赖其他库的情况,可以使用 target_link_libraries 命令。

代码语言:javascript
复制
add_executable(maindemo src/c/main.c)
target_link_libraries(maindemo math)
  • add_executable 用于定义一个可执行文件目标,这里定义了一个名为 maindemo 的可执行文件。
  • target_link_libraries 用于为目标(可执行文件或库)添加链接库。maindemo 是要链接库的目标名称,即第一行定义的可执行文件。这里为maindemo 可执行文件链接了一个math 库。

到此可以执行cmake 构建编译指令

完整的cmake 命令如下:

代码语言:javascript
复制
cmake_minimum_required(VERSION 3.12)
project(maindemo VERSION 1.0.0 LANGUAGES C CXX)

set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

add_compile_options(-Wall -Wextra -pedantic -Werror)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")

set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")

file(GLOB_RECURSE MATH_LIB_SRC
    src/c/*.c
)
add_library(math STATIC ${MATH_LIB_SRC})

add_executable(maindemo src/c/main.c)
target_link_libraries(maindemo math)

CMakeLists.txt 文件下执行cmake 构建和编译指令。

代码语言:javascript
复制
cmake -B cmake-demo
cmake --build cmake-demo
./cmake-demo/maindemo
  • cmake -B cmake-demo:用来初始化构建过程并生成构建系统文件,-B cmake-demo 表示构建路径为 cmake-demo,即生成的构建文件在 cmake-demo 路径下。
  • cmake --build cmake-demo:在生成的构建系统文件路径下执行编译项目。或者使用 make 指令,make 指令使用的是Makefile 文件。
  • ./cmake-demo/maindemo:执行二进制文件。

三、Cmake安装和打包

1、安装

可以通过 install 命令来安装生成的二进制文件。

代码语言:javascript
复制
install(TARGETS math demo
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

通过 TARGETS 参数指定需要安装的目标列表。

  • RUNTIME DESTINATION:可执行文件的安装目录;
  • LIBRARY DESTINATION:库文件的安装目录;
  • ARCHIVE DESTINATION:归档文件的安装目录。 指定CMAKE_INSTALL_PREFIX/usr/local,那么math库将会被安装到路径/usr/local/lib/目录下;而demo可执行文件则在/usr/local/bin目录下。
代码语言:javascript
复制
cmake -DCMAKE_INSTALL_PREFIX=/path/to/install
  • CMAKE_INSTALL_PREFIX 变量说明安装的路径。
2、打包

可以使用 CPack 模块来打包生成的二进制文件,该指令会在构建编译之后使用cpack 命令进行打包安装。也可以使用make 工具的指令 make package

代码语言:javascript
复制
include(CPack)

include 有如下命令:

命令

描述

CPACK_GENERATOR

打包使用的压缩工具,比如"ZIP"

CPACK_OUTPUT_FILE_PREFIX

打包安装的路径前缀

CPACK_INSTALL_PREFIX

打包压缩包的内部目录前缀

CPACK_PACKAGE_FILE_NAME

打包压缩包的名称(<项目名称>-<版本号>-<附加信息>),默认值由CPACK_PACKAGE_NAME、CPACK_PACKAGE_VERSION、CPACK_SYSTEM_NAME三部分构成

代码语言:javascript
复制
include(CPack)
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_NAME "CMakeTemplate")
set(CPACK_SET_DESTDIR ON)
set(CPACK_OUTPUT_FILE_PREFIX "/usr/local/package")
set(CPACK_INSTALL_PREFIX "bin/demo")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})

${PROJECT_VERSION}=v1.0.0,则打包文件的路径为 /usr/local/package/CMakeTemplate-1.0.0.zip,压缩包内的可执行文件位于 /usr/local/package/bin/demo/ 下。

四、单元测试

我们在 CMakeLists.txt 中通过命令 enable_testing() 或者 include(CTest) 来启用测试功能。再使用 add_test 命令添加测试用例,指定测试的名称和测试命令、参数。在构建编译完成后使用 ctest 命令行工具运行测试。

可以增加测试控制变量,可以通过 cmake -DCMAKE_TEMPLATE_ENABLE_TEST=ON 指令,在构建编译时开启单元测试。

代码语言:javascript
复制
option(CMAKE_TEMPLATE_ENABLE_TEST "Whether to enable unit tests" ON)
if (CMAKE_TEMPLATE_ENABLE_TEST)
    message(STATUS "Unit tests enabled")
    enable_testing()
endif()

1、定义单元测试源码

在开发项目时,通常我们会编写一些单元测试代码。这里针对一个CMake Project的模板仓库增加一个单元测试文件。

一般定义单元测试返回值非零时,单元测试未通过。以下是单元测试 test_add.c 文件的源码:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>

#include "math/add.h"

int main(int argc, char* argv[]) {
	if (argc != 4) {
		printf("Usage: test_add v1 v2 expected\n");
		return 1;
	}
	
	int x = atoi(argv[1]);
	int y = atoi(argv[2]);
	int expected = atoi(argv[3]);
	int res = add_int(x, y);
	
	if (res != expected) {
		return 1;
	} else {
		return 0;
	}
}

2、cmake增加测试

先生成单元测试可执行脚本,再通过 add_test 命令来添加测试。

代码语言:javascript
复制
add_executable(test_add test/c/test_add.c)
target_link_libraries(test_add math)
add_test(NAME test_add COMMAND test_add 10 24 34)
  • add_executable(test_add test/c/test_add.c):创建了一个名为 test_add 的可执行目标,即一个可执行程序,源代码路径为 test/c/test_add.c
  • target_link_libraries(test_add math):指定 test_add 可执行目标需要链接到 math 库。
  • add_test(NAME test_add COMMAND test_add 10 24 34):定义了一个名为 test_add 的测试。COMMAND test_add 10 24 34 指定了测试运行时将要执行的命令和参数,即当运行 ctest 命令时,test_add 程序将被执行,传入 102434 作为命令行参数。
3、执行Cmake测试

可以使用 ctest 命令来执行测试,例如:

代码语言:javascript
复制
cmake -B cmake-demo
cmake --build cmake-demo
cd cmake-demo && ctest && cd -
  • cd cmake-demo && ctest && cd -:执行单元测试
    • cd cmake-demo:切换当前工作目录到 cmake-demo 构建目录;
    • ctest:在构建目录中运行 CTest,CTest 是 CMake 的测试驱动程序,用于运行项目中的测试。
    • cd -:切换回原先的工作目录。这个命令是可选的,即在运行单元测试后返回到原来的目录。

参考

一个CMake Project的模板仓库 Cmake中文实战教程

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、基础配置
    • 1、设置CMake 版本要求
      • 2、项目版本规定
        • 3、配置编译选项
          • 4、配置编译类型
            • 5、添加全局宏定义
              • 6、添加 include 目录
              • 二、编译目标文件——示例演示
                • 1、编译静态库
                  • 三、Cmake安装和打包
              • 四、单元测试
                • 1、定义单元测试源码
                  • 2、cmake增加测试
                  • 参考
                  相关产品与服务
                  腾讯云服务器利旧
                  云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档