这两者的关系其实非常好理解,我们可以用一个**"盖房子"**的例子来打比方。
简单来说:CMake 是"设计师",Makefile 是"施工图纸",Make 是"施工队"。
🏠 直白的大白话解释
想象你要盖一栋房子(编译一个 C++ 项目):
-
CMakeLists.txt(你的需求) :
这是你写给设计师的需求文档。你在上面写:"我要一个 3 室 2 厅的房子,要有落地窗,风格要现代。"(这对应你的项目配置:我要用 C++17,我有 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 生成 Makefile :
CMake 的工作就是写 Makefile。当你运行cmake .时,它就在帮你生成这个文件。 - Make 执行 Makefile :
Make 的工作是读 Makefile。当你运行make时,它按照 Makefile 里的指令去调用编译器。
🆚 为什么要用 CMake,直接写 Makefile 不行吗?
行,但是太累了。
- 手写 Makefile :
就像你自己画施工图纸。如果房子只有 10 平米(几个文件),你自己画很快。但如果要盖摩天大楼(几千个文件,复杂的依赖),或者你要在不同的地方盖(Linux、Windows、Mac),手写图纸会累死人,而且容易出错。 - 用 CMake :
你只需要写一份需求文档(CMakeLists.txt)。不管你要盖什么房子,不管在哪里盖,CMake 都能自动帮你生成对应的图纸。
📌 总结
- CMake :是跨平台的构建生成器。它负责把"人话"(配置)翻译成"机器话"(Makefile)。
- Makefile :是构建脚本。它记录了具体的编译步骤。
- Make :是执行工具。它负责执行 Makefile 里的命令。
你的工作流程通常是:
写 CMakeLists.txt -> 运行 cmake 生成 Makefile -> 运行 make 编译代码。
bash
# 指定 CMake 的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 定义项目名称和版本
project(main VERSION 1.0)
# 设置 C++ 标准为 C++17,并要求编译器必须支持
set(CMAKE_CXX_STANDARD 17)
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:/C++11 这个项目给出它们实际对应的值。
核心配置部分
cmake_minimum_required(VERSION 3.10)
- 含义:声明运行此 CMake 脚本所需的最低版本。如果用户的 CMake 版本低于 3.10,CMake 会报错并停止运行。
- 作用:确保脚本中使用的语法特性在当前环境中受支持。
- 当前状态:你的电脑上安装的 CMake 版本必须 ≥ 3.10。
project(main VERSION 1.0)
- 含义:定义项目名称和版本号。这是 CMake 脚本中最重要的指令之一,它会初始化一系列变量。
- 关键字 :
main:这是你给项目起的名字。VERSION 1.0:项目的版本号。
- 生成的变量 :
PROJECT_NAME:值为"main"。PROJECT_VERSION:值为"1.0"。PROJECT_SOURCE_DIR:值为"E:/C++11"(即包含此文件的根目录)。
set(CMAKE_CXX_STANDARD 17)
- 含义:告诉 CMake 使用 C++17 标准来编译代码。
- 作用 :相当于在 g++ 编译器后面加了
-std=c++17参数。这让你可以使用现代 C++ 的特性(如auto、filesystem等)。
set(CMAKE_CXX_STANDARD_REQUIRED ON)
- 含义:强制要求编译器支持 C++17。
- 作用:如果编译器太老(不支持 C++17),CMake 会直接报错,而不是悄悄降级使用旧标准(如 C++98),避免莫名其妙的编译错误。
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
- 含义 :生成
compile_commands.json文件。 - 作用:这个文件包含完整的编译命令。VSCode 的 C/C++ 插件读取它,就能精准地知道头文件在哪里、宏定义是什么,从而提供完美的代码跳转和补全功能。
源文件与路径配置
include_directories(${PROJECT_SOURCE_DIR}/include)
- 含义:添加头文件搜索路径。
- 关键字 :
include_directories:指令,用于指定头文件目录。${PROJECT_SOURCE_DIR}:变量引用,取值为"E:/C++11"。
- 实际路径 :
E:/C++11/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:/C++11/src/main.cpp;E:/C++11/src/test.cpp"。 - 注意 :这种方式会自动把
src下所有.cpp加入编译,不用每次新增文件都改 CMake 文件,很方便。
add_executable(${PROJECT_NAME} ${SOURCES})
- 含义:定义一个可执行文件目标。
- 关键字 :
add_executable:指令,生成.exe(Windows)或无后缀可执行文件(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:/C++11/bin"。
- 为什么比旧版好? :
- 旧版(
EXECUTABLE_OUTPUT_PATH)是全局变量,会影响项目里所有的可执行文件。 - 新版(你用的这个) :只针对名为
main的这个程序生效。如果你的项目将来变大了,包含多个程序,你可以分别为它们设置不同的输出目录,互不干扰。
- 旧版(
总结
你的这份 CMake 脚本逻辑非常清晰:
- 定标准:要求 CMake 3.10+,使用 C++17。
- 找文件 :自动扫描
src下的代码,指定include目录。 - 生程序 :编译生成名为
main的程序。 - 定位置 :把生成的程序扔进
E:/C++11/bin文件夹里。
这套配置在 Windows 和 Linux 下通用,直接运行 CMake: Build 即可。