news 2026/4/30 16:33:19

【CMake】[第十三篇] 理解CMake目标这个概念:从Makefile到CMake的思维转换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【CMake】[第十三篇] 理解CMake目标这个概念:从Makefile到CMake的思维转换

理解CMake目标这个概念:从Makefile到CMake的思维转换

📖 前言

如果你从 Makefile 转向 CMake,可能会对"目标"(Target)这个概念感到困惑。在 Makefile 中,目标通常指的是要生成的文件;而在 CMake 中,目标是一个更抽象的概念。本文将深入探讨 CMake 中的目标概念,并与 Makefile 进行对比,帮助你更好地理解和使用 CMake。


🎯 什么是CMake目标?

核心定义

在 CMake 中,**目标(Target)**是一个抽象的构建单元,代表一个可执行文件、库文件或自定义任务。目标不仅仅是文件,它还包含了:

  • 源文件列表:哪些文件需要编译
  • 编译选项:如何编译(编译器标志、包含目录等)
  • 链接选项:如何链接(链接库、链接器标志等)
  • 依赖关系:依赖哪些其他目标
  • 属性:各种元数据(版本、输出目录等)

目标的类型

CMake 中有三种主要的目标类型:

  1. 可执行文件目标:由add_executable()创建
  2. 库目标:由add_library()创建(静态库、动态库、接口库)
  3. 自定义目标:由add_custom_target()创建

📚 Makefile 中的"目标"概念

Makefile 目标的基本形式

在 Makefile 中,目标通常指的是要生成的文件

# Makefile 示例 app: main.o utils.o g++ main.o utils.o -o app main.o: main.cpp g++ -c main.cpp -o main.o utils.o: utils.cpp g++ -c utils.cpp -o utils.o

特点

  • 目标通常是文件名(如appmain.o
  • 每个目标对应一个规则(rule)
  • 规则定义了如何从依赖生成目标文件
  • 目标是文件导向

Makefile 中的伪目标(Phony Target)

Makefile 也支持不生成文件的目标,称为"伪目标":

.PHONY: clean install clean: rm -f *.o app install: app cp app /usr/local/bin/

特点

  • 使用.PHONY声明伪目标
  • 伪目标不生成文件,只执行命令
  • 类似于 CMake 中的自定义目标

🔄 CMake 目标 vs Makefile 目标

对比表

特性Makefile 目标CMake 目标
本质文件或伪目标抽象的构建单元
定义方式规则(rule)函数调用(add_executable等)
包含内容依赖和命令源文件、选项、依赖、属性
命名通常是文件名逻辑名称(可以是文件名)
依赖管理显式列出依赖自动管理 + 显式声明
跨平台需要手动处理自动处理
属性丰富的属性系统

💡 实际对比示例

示例1:创建可执行文件

Makefile 方式
# Makefile CXX = g++ CXXFLAGS = -std=c++11 -Wall SOURCES = main.cpp utils.cpp OBJECTS = $(SOURCES:.cpp=.o) TARGET = my_app $(TARGET): $(OBJECTS) $(CXX) $(OBJECTS) -o $(TARGET) main.o: main.cpp utils.h $(CXX) $(CXXFLAGS) -c main.cpp -o main.o utils.o: utils.cpp utils.h $(CXX) $(CXXFLAGS) -c utils.cpp -o utils.o clean: rm -f $(OBJECTS) $(TARGET) .PHONY: clean

特点

  • 需要手动管理每个.o文件的规则
  • 需要手动指定编译命令和选项
  • 依赖关系需要显式声明
  • 跨平台需要条件判断
CMake 方式
# CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(MyApp) set(CMAKE_CXX_STANDARD 11) # 创建一个目标 add_executable(my_app main.cpp utils.cpp ) # 为目标设置属性 target_include_directories(my_app PRIVATE .) target_compile_options(my_app PRIVATE -Wall)

特点

  • 只需要列出源文件,CMake 自动处理编译
  • 不需要手动管理.o文件
  • 依赖关系自动检测
  • 跨平台自动处理

对比分析

  • Makefile:关注文件命令
  • CMake:关注目标属性

示例2:创建库

Makefile 方式
# Makefile - 静态库 LIB_NAME = libmath.a LIB_SOURCES = math.cpp LIB_OBJECTS = $(LIB_SOURCES:.cpp=.o) $(LIB_NAME): $(LIB_OBJECTS) ar rcs $(LIB_NAME) $(LIB_OBJECTS) math.o: math.cpp math.h g++ -c math.cpp -o math.o # Makefile - 动态库 SHARED_LIB = libmath.so $(SHARED_LIB): math.o g++ -shared -fPIC -o $(SHARED_LIB) math.o

特点

  • 静态库需要ar命令
  • 动态库需要-shared -fPIC选项
  • 不同平台命令不同(Windows vs Linux)
CMake 方式
# CMakeLists.txt # 静态库 add_library(math_lib STATIC math.cpp) # 动态库 add_library(math_lib SHARED math.cpp) # 接口库(仅头文件) add_library(math_lib INTERFACE) target_include_directories(math_lib INTERFACE .)

特点

  • 一个函数调用即可创建库
  • CMake 自动选择合适的工具和选项
  • 跨平台自动处理

对比分析

  • Makefile:需要了解底层工具(arg++ -shared
  • CMake:抽象化,不需要了解底层细节

示例3:目标依赖关系

Makefile 方式
# Makefile app: main.o libmath.a g++ main.o -L. -lmath -o app main.o: main.cpp g++ -c main.cpp -o main.o libmath.a: math.o ar rcs libmath.a math.o math.o: math.cpp g++ -c math.cpp -o math.o

特点

  • 依赖关系通过文件名显式声明
  • 链接时需要手动指定库路径和库名
  • 需要了解链接器的选项(-L-l
CMake 方式
# CMakeLists.txt # 创建库目标 add_library(math_lib STATIC math.cpp) # 创建可执行文件目标 add_executable(app main.cpp) # 链接库(自动处理路径和依赖) target_link_libraries(app PRIVATE math_lib)

特点

  • 使用目标名称,而不是文件名
  • CMake 自动处理库路径
  • 自动传递依赖关系

对比分析

  • Makefile:依赖关系是文件到文件
  • CMake:依赖关系是目标到目标

🔍 CMake 目标的优势

1. 抽象层次更高

Makefile

# 需要知道具体的文件路径和命令 app: main.o utils.o g++ main.o utils.o -o app

CMake

# 只需要逻辑名称和源文件 add_executable(app main.cpp utils.cpp)

2. 属性系统

CMake 目标有丰富的属性系统:

add_executable(my_app main.cpp) # 设置包含目录 target_include_directories(my_app PRIVATE include) # 设置编译选项 target_compile_options(my_app PRIVATE -Wall -Wextra) # 设置链接库 target_link_libraries(my_app PRIVATE math_lib) # 设置输出目录 set_target_properties(my_app PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )

Makefile 中:这些都需要手动管理变量和命令。

3. 依赖传递

CMake 支持依赖传递:

# 库目标 add_library(math_lib STATIC math.cpp) target_include_directories(math_lib PUBLIC include) # PUBLIC 表示传递 # 可执行文件目标 add_executable(app main.cpp) target_link_libraries(app PRIVATE math_lib) # 自动获得 include 目录

Makefile 中:需要手动传递所有选项。

4. 跨平台支持

# CMake 自动处理平台差异 add_library(my_lib SHARED src.cpp) # Windows: 生成 .dll 和 .lib # Linux: 生成 .so # macOS: 生成 .dylib

Makefile 中:需要条件判断:

ifeq ($(OS),Windows_NT) LIB_EXT = .dll else LIB_EXT = .so endif

🎓 思维转换:从文件到目标

Makefile 思维(文件导向)

文件 → 规则 → 命令 → 生成文件

关注点

  • 这个文件需要什么依赖?
  • 用什么命令生成这个文件?
  • 文件路径是什么?

CMake 思维(目标导向)

目标 → 属性 → 自动生成规则 → 构建

关注点

  • 这个目标需要什么源文件?
  • 这个目标有什么属性?
  • 这个目标依赖哪些其他目标?

📝 实际应用对比

场景:多目录项目

Makefile 方式
# 主 Makefile SUBDIRS = core utils app all: for dir in $(SUBDIRS); do \ $(MAKE) -C $$dir; \ done # core/Makefile core/libcore.a: core.o ar rcs core/libcore.a core.o # utils/Makefile utils/libutils.a: utils.o ar rcs utils/libutils.a utils.o # app/Makefile app/app: app.o ../core/libcore.a ../utils/libutils.a g++ app.o -L../core -L../utils -lcore -lutils -o app/app

问题

  • 需要手动管理路径
  • 需要手动管理依赖顺序
  • 跨目录依赖复杂
CMake 方式
# 主 CMakeLists.txt add_subdirectory(core) add_subdirectory(utils) add_subdirectory(app) # core/CMakeLists.txt add_library(core STATIC core.cpp) target_include_directories(core PUBLIC .) # utils/CMakeLists.txt add_library(utils STATIC utils.cpp) target_link_libraries(utils PUBLIC core) # 依赖 core # app/CMakeLists.txt add_executable(app main.cpp) target_link_libraries(app PRIVATE core utils) # 自动处理依赖

优势

  • 路径自动管理
  • 依赖顺序自动处理
  • 跨目录依赖简单

🔧 CMake 目标的执行

构建目标

# 构建所有目标cmake--build.# 构建特定目标cmake--build.--targetmy_app cmake--build.--targetmath_lib

Makefile 等价

# 构建所有目标make# 构建特定目标makemy_appmakemath_lib

目标类型对比

CMake 目标类型Makefile 等价说明
add_executable()可执行文件规则生成可执行文件
add_library(STATIC)ar rcs规则生成静态库
add_library(SHARED)g++ -shared规则生成动态库
add_custom_target().PHONY目标不生成文件的任务

💭 常见误解

误解1:CMake 目标就是文件名

错误理解

add_executable(my_app main.cpp) # 认为 my_app 就是文件名

正确理解

add_executable(my_app main.cpp) # my_app 是目标的逻辑名称 # 实际文件名可能是 my_app.exe (Windows) 或 my_app (Linux)

误解2:CMake 目标必须对应文件

错误理解:每个目标都必须生成一个文件。

正确理解

# 自定义目标不生成文件 add_custom_target(docs COMMAND doxygen Doxyfile ) # 接口库也不生成文件 add_library(header_only INTERFACE) target_include_directories(header_only INTERFACE include)

误解3:CMake 目标就是 Makefile 目标

错误理解:CMake 目标 = Makefile 目标

正确理解

  • Makefile 目标:主要是文件,关注"如何生成文件"
  • CMake 目标:是抽象构建单元,关注"如何构建目标"

🎯 最佳实践

1. 使用有意义的目标名称

# 好:有意义的名称 add_executable(calculator_app main.cpp) add_library(math_utils STATIC math.cpp) # 不好:无意义的名称 add_executable(app main.cpp) add_library(lib STATIC math.cpp)

2. 利用目标的属性系统

add_executable(my_app main.cpp) # 集中管理目标属性 target_include_directories(my_app PRIVATE include) target_compile_options(my_app PRIVATE -Wall) target_link_libraries(my_app PRIVATE math_lib)

3. 使用 PUBLIC/PRIVATE/INTERFACE

# 库目标:PUBLIC 表示接口需要,也传递给使用者 add_library(math_lib STATIC math.cpp) target_include_directories(math_lib PUBLIC include) # 可执行文件:PRIVATE 表示仅自己使用 add_executable(app main.cpp) target_include_directories(app PRIVATE src) target_link_libraries(app PRIVATE math_lib) # 自动获得 include 目录

📊 总结对比

核心差异

方面MakefileCMake
思维模式文件导向目标导向
抽象层次低(接近命令)高(抽象构建单元)
依赖管理手动显式自动 + 显式
跨平台需要手动处理自动处理
属性系统丰富
学习曲线陡峭(需要了解工具)平缓(高级抽象)

何时使用 Makefile?

  1. 简单项目:只有几个源文件
  2. 学习目的:想深入理解构建过程
  3. 特殊需求:需要非常精细的控制
  4. 无 CMake 环境:某些嵌入式或特殊环境

何时使用 CMake?

  1. 复杂项目:多目录、多库、多目标
  2. 跨平台项目:需要在多个平台构建
  3. 团队协作:标准化构建流程
  4. 现代 C++ 项目:需要依赖管理、测试、安装等

🚀 迁移建议

如果你熟悉 Makefile,迁移到 CMake 时:

  1. 改变思维:从"文件"转向"目标"
  2. 利用抽象:让 CMake 处理底层细节
  3. 使用属性:充分利用目标的属性系统
  4. 理解依赖:理解 PUBLIC/PRIVATE/INTERFACE 的区别

📚 进一步学习

  • CMake 目标属性:get_target_property()set_target_properties()
  • 生成器表达式:$<TARGET_FILE:...>$<TARGET_PROPERTY:...>
  • 导入目标:find_package()创建的目标
  • 别名目标:add_library(alias ALIAS target)

💡 结语

CMake 的"目标"概念是对 Makefile "目标"的抽象和扩展。理解这个概念,是从 Makefile 思维转向 CMake 思维的关键。目标不仅仅是文件,它是一个包含源文件、选项、依赖和属性的完整构建单元。掌握这个概念,你将能够更高效地使用 CMake 管理复杂的项目。


参考资源

  • CMake 官方文档 - Targets
  • CMake 官方文档 - add_executable
  • CMake 官方文档 - add_library
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/29 21:05:05

Android串口通信终极指南:从开发痛点到企业级解决方案

Android串口通信终极指南&#xff1a;从开发痛点到企业级解决方案 【免费下载链接】Android-Serialport 移植谷歌官方串口库&#xff0c;仅支持串口名称及波特率&#xff0c;该项目添加支持校验位、数据位、停止位、流控配置项 项目地址: https://gitcode.com/gh_mirrors/an/…

作者头像 李华
网站建设 2026/4/27 0:11:19

智能票务操作手册:大麦网自动化购票的零基础入门指南

智能票务操作手册&#xff1a;大麦网自动化购票的零基础入门指南 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase 还在为心仪的演出门票一票难求而烦恼吗&#xff1f;我们共同…

作者头像 李华
网站建设 2026/4/27 9:13:05

WorkshopDL终极指南:三步解锁Steam创意工坊全模组下载

还在为Epic、GOG平台的游戏无法使用Steam创意工坊模组而烦恼吗&#xff1f;&#x1f680; WorkshopDL这款开源神器让跨平台玩家也能畅享海量模组资源。无论你在哪个平台购买游戏&#xff0c;只需简单三步配置&#xff0c;就能一键下载1000款游戏的创意工坊内容。 【免费下载链接…

作者头像 李华
网站建设 2026/4/25 5:26:25

Applite:零基础轻松管理macOS应用的终极指南

Applite&#xff1a;零基础轻松管理macOS应用的终极指南 【免费下载链接】Applite User-friendly GUI macOS application for Homebrew Casks 项目地址: https://gitcode.com/gh_mirrors/ap/Applite 还在为复杂的终端命令而头疼吗&#xff1f;Applite这款macOS神器将彻底…

作者头像 李华
网站建设 2026/4/28 4:15:39

Windows流媒体服务器SRS:从零开始搭建专业级视频传输平台

Windows流媒体服务器SRS&#xff1a;从零开始搭建专业级视频传输平台 【免费下载链接】srs-windows 项目地址: https://gitcode.com/gh_mirrors/sr/srs-windows 想要在Windows系统上快速构建一个功能完善的流媒体服务器吗&#xff1f;SRS Windows版本为你提供了完整的解…

作者头像 李华
网站建设 2026/4/28 9:20:37

为什么你的模型总过拟合?R语言交叉验证代码优化全解析

第一章&#xff1a;为什么你的模型总过拟合&#xff1f;过拟合是机器学习实践中最常见的问题之一。当模型在训练数据上表现极佳&#xff0c;但在验证或测试数据上性能显著下降时&#xff0c;通常意味着模型已经“死记硬背”了训练样本的细节&#xff0c;而失去了泛化能力。理解…

作者头像 李华