C++之 CMake、CMakeLists.txt、Makefile
这两者的关系其实非常好理解我们可以用一个**“盖房子”**的例子来打比方。简单来说CMake 是“设计师”Makefile 是“施工图纸”Make 是“施工队”。 直白的大白话解释想象你要盖一栋房子编译一个 C 项目CMakeLists.txt你的需求这是你写给设计师的需求文档。你在上面写“我要一个 3 室 2 厅的房子要有落地窗风格要现代。”这对应你的项目配置我要用 C17我有 main.cpp 和 utils.cpp 这些文件我要链接 OpenGL 库。CMake设计师/翻译官CMake 读取你的需求文档CMakeLists.txt然后根据你的环境你是住在 Linux 还是 Windows画出了一套详细的施工图纸Makefile。如果你在 Linux它会画出适合 Linux 的图纸。如果你在 Windows它会画出适合 Windows 的图纸比如 Visual Studio 的项目文件。Makefile施工图纸/说明书这是一份非常详细的操作指南。上面写着“第一步先倒水泥第二步砌墙第三步刷漆。”这对应具体的编译命令先用 g 编译 main.cpp 生成 main.o再链接…。注意Makefile 自己不会干活它只是被阅读的。Make施工队/工头Make 是一个工具它拿着Makefile施工图纸指挥具体的工人编译器 g去干活。它会检查“墙是不是已经砌好了没砌好就赶紧砌砌好了就不用管了。” 它们的具体关系CMake 生成 MakefileCMake 的工作就是写Makefile。当你运行cmake .时它就在帮你生成这个文件。Make 执行 MakefileMake 的工作是读Makefile。当你运行make时它按照 Makefile 里的指令去调用编译器。 为什么要用 CMake直接写 Makefile 不行吗行但是太累了。手写 Makefile就像你自己画施工图纸。如果房子只有 10 平米几个文件你自己画很快。但如果要盖摩天大楼几千个文件复杂的依赖或者你要在不同的地方盖Linux、Windows、Mac手写图纸会累死人而且容易出错。用 CMake你只需要写一份需求文档CMakeLists.txt。不管你要盖什么房子不管在哪里盖CMake 都能自动帮你生成对应的图纸。 总结CMake是跨平台的构建生成器。它负责把“人话”配置翻译成“机器话”Makefile。Makefile是构建脚本。它记录了具体的编译步骤。Make是执行工具。它负责执行 Makefile 里的命令。你的工作流程通常是写CMakeLists.txt- 运行cmake生成Makefile- 运行make编译代码。# 指定 CMake 的最低版本要求cmake_minimum_required(VERSION3.10)# 定义项目名称和版本project(main VERSION1.0)# 设置 C 标准为 C17并要求编译器必须支持set(CMAKE_CXX_STANDARD17)set(CMAKE_CXX_STANDARD_REQUIRED ON)# 导出编译命令数据库方便 VSCode 的 C/C 插件进行代码分析set(CMAKE_EXPORT_COMPILE_COMMANDS ON)# 添加头文件搜索路径include_directories(${PROJECT_SOURCE_DIR}/include)# 收集 src 目录下的所有 .cpp 源文件file(GLOB SOURCES${PROJECT_SOURCE_DIR}/src/*.cpp)# 定义要生成的可执行文件及其源文件add_executable(${PROJECT_NAME}${SOURCES})# 设置输出路径set_target_properties(${PROJECT_NAME}PROPERTIES RUNTIME_OUTPUT_DIRECTORY${PROJECT_SOURCE_DIR}/bin)这份CMakeLists.txt写得非常规范且现代比上一版更健壮。特别是你使用了set_target_properties来指定输出路径这是比旧版EXECUTABLE_OUTPUT_PATH更推荐的做法。下面我为你逐行拆解每一行代码、关键字的含义并结合你E:/C11这个项目给出它们实际对应的值。核心配置部分cmake_minimum_required(VERSION 3.10)含义声明运行此 CMake 脚本所需的最低版本。如果用户的 CMake 版本低于 3.10CMake 会报错并停止运行。作用确保脚本中使用的语法特性在当前环境中受支持。当前状态你的电脑上安装的 CMake 版本必须 ≥ 3.10。project(main VERSION 1.0)含义定义项目名称和版本号。这是 CMake 脚本中最重要的指令之一它会初始化一系列变量。关键字main这是你给项目起的名字。VERSION 1.0项目的版本号。生成的变量PROJECT_NAME值为main。PROJECT_VERSION值为1.0。PROJECT_SOURCE_DIR值为E:/C11即包含此文件的根目录。set(CMAKE_CXX_STANDARD 17)含义告诉 CMake 使用 C17 标准来编译代码。作用相当于在 g 编译器后面加了-stdc17参数。这让你可以使用现代 C 的特性如auto、filesystem等。set(CMAKE_CXX_STANDARD_REQUIRED ON)含义强制要求编译器支持 C17。作用如果编译器太老不支持 C17CMake 会直接报错而不是悄悄降级使用旧标准如 C98避免莫名其妙的编译错误。set(CMAKE_EXPORT_COMPILE_COMMANDS ON)含义生成compile_commands.json文件。作用这个文件包含完整的编译命令。VSCode 的 C/C 插件读取它就能精准地知道头文件在哪里、宏定义是什么从而提供完美的代码跳转和补全功能。源文件与路径配置include_directories(${PROJECT_SOURCE_DIR}/include)含义添加头文件搜索路径。关键字include_directories指令用于指定头文件目录。${PROJECT_SOURCE_DIR}变量引用取值为E:/C11。实际路径E:/C11/include。作用当你在代码中写#include myheader.h时编译器会去这个目录下查找。file(GLOB SOURCES ${PROJECT_SOURCE_DIR}/src/*.cpp)含义使用通配符查找文件。关键字file(GLOB ...)文件操作指令用于匹配文件路径。SOURCES你自定义的一个变量名用来存放找到的文件列表。${PROJECT_SOURCE_DIR}/src/*.cpp匹配规则。实际值假设你src文件夹里有main.cpp和test.cpp那么SOURCES变量的值就是E:/C11/src/main.cpp;E:/C11/src/test.cpp。注意这种方式会自动把src下所有.cpp加入编译不用每次新增文件都改 CMake 文件很方便。add_executable(${PROJECT_NAME} ${SOURCES})含义定义一个可执行文件目标。关键字add_executable指令生成.exeWindows或无后缀可执行文件Linux。${PROJECT_NAME}值为main这是生成的程序的名字。${SOURCES}值为上面file(GLOB ...)找到的所有.cpp文件。作用告诉 CMake“请把SOURCES里的这些代码编译成一个叫main的程序”。输出路径配置关键点set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)含义设置特定目标的属性。这里专门设置“运行时输出目录”。关键字set_target_properties指令用于精细化控制目标的属性。${PROJECT_NAME}目标名即main。PROPERTIES关键字后面跟属性名和值。RUNTIME_OUTPUT_DIRECTORY属性名专门控制可执行文件.exe的输出位置。${PROJECT_SOURCE_DIR}/bin属性值即E:/C11/bin。为什么比旧版好旧版EXECUTABLE_OUTPUT_PATH是全局变量会影响项目里所有的可执行文件。新版你用的这个只针对名为main的这个程序生效。如果你的项目将来变大了包含多个程序你可以分别为它们设置不同的输出目录互不干扰。总结你的这份 CMake 脚本逻辑非常清晰定标准要求 CMake 3.10使用 C17。找文件自动扫描src下的代码指定include目录。生程序编译生成名为main的程序。定位置把生成的程序扔进E:/C11/bin文件夹里。这套配置在 Windows 和 Linux 下通用直接运行CMake: Build即可。