第一个 CMake 项目:最小 CMakeLists.txt 到底写了什么
欢迎拜访雾里看山-CSDN博客本篇主题第一个 CMake 项目最小CMakeLists.txt到底写了什么发布时间2026.9.3隶属专栏CMake目录这一篇的目标最小的工程长什么样文件结构main.cppCMakeLists.txt逐行拆解第一行cmake_minimum_required第二行project关于语言的默认值关于 C 和 CXXVERSION 字段第三行add_executable把它跑起来容易踩的几个坑坑 1文件名大小写坑 2忘记写 cmake_minimum_required坑 3target 名和源文件同名坑 4相对路径的理解坑 5源文件里有 .c 又想当 C 编加点东西看看效果1. 加多个源文件2. 加头文件目录3. 加 C 标准4. 加宏定义target 是 CMake 的核心把最小工程再压缩到极致这一篇抓什么总结这一篇的目标上一篇跑通了命令行工作流。这一篇就把CMakeLists.txt真正打开看看一个最小可运行的 CMake 工程到底由哪些行组成每一行的作用是什么。虽然只有几行但里面每一行都有自己的角色。把这几行吃透后面再加 target、加依赖、装包都不会陌生。最小的工程长什么样文件结构一个最小 CMake 工程通常长这样hello/ ├── CMakeLists.txt └── main.cppmain.cpp先写一个简单的 C 程序#includeiostreamintmain(){std::coutHello, CMake!std::endl;return0;}CMakeLists.txt对应写一个最小版本cmake_minimum_required(VERSION 3.15) project(HelloCMake LANGUAGES CXX) add_executable(hello main.cpp)只有三行命令但它已经是一个完整的、可构建的工程。逐行拆解第一行cmake_minimum_requiredcmake_minimum_required(VERSION 3.15)这一行的作用是声明这个项目至少需要哪个版本的 CMake 才能正常构建。为什么要写这一行CMake 自己也在演进每个版本会引入新的命令、新的策略policy、新的行为。如果项目用到了一些新特性而对方的 CMake 版本太旧就可能出问题。所以这是兼容性保护不是“版本号越高越好”选择版本的原则是用到你需要的特性的最低版本比如要用target_link_libraries(... PRIVATE/PUBLIC/INTERFACE)的现代语义至少 3.x 任意版本都行要用FetchContent需要 3.14要用imported targets的标准改进需要 3.15要用CMakePresets.json需要 3.19如果团队用的 CMake 都在 3.20 以上那直接写 3.20 也没问题。另外cmake_minimum_required必须是CMakeLists.txt中的第一行有效命令注释和空行不算否则 CMake 会报错。第二行projectproject(HelloCMake LANGUAGES CXX)这一行做几件事给项目取一个名字HelloCMake声明这个项目使用的语言LANGUAGES CXX表示只用 C同时CMake 会自动给一些变量赋值PROJECT_NAME→HelloCMakeCMAKE_PROJECT_NAME→ 顶层项目名PROJECT_SOURCE_DIR、PROJECT_BINARY_DIR等关于语言的默认值如果不写LANGUAGESCMake 默认是空也就是说不会自动启用任何语言的检查。但很多人会看到这种写法project(HelloCMake CXX)这种是不带关键字的写法靠位置参数识别。两种写法等价project(HelloCMake CXX) project(HelloCMake LANGUAGES CXX)推荐带LANGUAGES关键字更清楚。关于 C 和 CXX如果工程同时有 C 和 Cproject(MyApp LANGUAGES C CXX)如果只有 Cproject(MyApp LANGUAGES C)VERSION 字段project还可以接受一个版本号project(HelloCMake VERSION 1.0.0 LANGUAGES CXX)它会顺带设置PROJECT_VERSION→1.0.0PROJECT_VERSION_MAJOR→1PROJECT_VERSION_MINOR→0PROJECT_VERSION_PATCH→0后面做打包、安装、版本头文件生成时经常会用到这些变量。第三行add_executableadd_executable(hello main.cpp)这一行是 CMake 中最关键的一行之一作用是声明一个可执行文件目标。把它拆开看hellotarget 名目标名也是最终生成的产物名在 Linux 上是helloWindows 上是hello.exemain.cpp构成这个可执行文件的源文件列表执行完这一行之后CMake 内部就建立了一个名为hello的 target后续可以围绕它做很多事情链接库加头文件目录加编译选项加宏定义不过这些是后面的内容这一篇先建立“target 是 CMake 里的第一公民”这个概念。把它跑起来假设工程根目录是hello/执行cdhello cmake-S.-Bbuild-GNinja cmake--buildbuild执行完后hello/ ├── CMakeLists.txt ├── main.cpp └── build/ ├── build.ninja └── hello 可执行文件运行./build/hello输出Hello, CMake!至此一个最小可运行 CMake 工程就跑通了。容易踩的几个坑坑 1文件名大小写在 Linux/macOS 上CMakeLists.txt必须大小写完全一致CMakeLists.txt不是cmakeLists.txt也不是CMakelists.txt写错大小写CMake 直接报错Could not find file: ... CMakeLists.txt。坑 2忘记写cmake_minimum_required没写时CMake 会用一个非常老的默认行为很多现代写法会出问题。所以任何CMakeLists.txt都应该先写这一行。坑 3target 名和源文件同名下面这种写法是错的add_executable(main.cpp main.cpp)target 名和文件名重复会导致 CMake 把第一个参数当成 target源文件列表变成(main.cpp)一个空名字 一个 main.cpp逻辑混乱。正确写法add_executable(my_app main.cpp)或者add_executable(${PROJECT_NAME} main.cpp)坑 4相对路径的理解add_executable(hello main.cpp)中的main.cpp是相对于当前CMakeLists.txt所在目录的而不是当前执行cmake命令时的目录。所以无论你在哪里调用cmake -S源文件路径都是从CMakeLists.txt自身算起的。这一点在后面多目录工程里尤其重要。坑 5源文件里有.c又想当 C 编如果main.cpp实际写的是 C 代码CMake 默认会按CXXC处理可能出现奇怪错误。这种情况要么把扩展名改成.c并在project里加LANGUAGES C或者明确加set_source_files_properties(main.cpp PROPERTIES LANGUAGE CXX)加点东西看看效果下面在最小工程基础上做一些最常见的扩展方便感受 CMake 描述能力。1. 加多个源文件cmake_minimum_required(VERSION 3.15) project(HelloCMake LANGUAGES CXX) add_executable(hello main.cpp greet.cpp )CMake 会把它们一起编译进同一个可执行文件。2. 加头文件目录cmake_minimum_required(VERSION 3.15) project(HelloCMake LANGUAGES CXX) add_executable(hello main.cpp) target_include_directories(hello PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)这时hello/ ├── CMakeLists.txt ├── main.cpp ├── greet.cpp └── include/ └── greet.hgreet.cpp就可以这样写#includegreet.h#includeiostreamvoidgreet(conststd::stringname){std::coutHello, name!std::endl;}3. 加 C 标准cmake_minimum_required(VERSION 3.15) project(HelloCMake LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_executable(hello main.cpp)或者更推荐的做法是写到 target 上cmake_minimum_required(VERSION 3.15) project(HelloCMake LANGUAGES CXX) add_executable(hello main.cpp) target_compile_features(hello PRIVATE cxx_std_17)这两个写法效果类似后者更符合 target-first 的思想。4. 加宏定义target_compile_definitions(hello PRIVATE USE_FANCY_LOG1)源码里就可以#ifdefUSE_FANCY_LOGstd::cout[FANCY] Hello, CMake!std::endl;#elsestd::coutHello, CMake!std::endl;#endiftarget 是 CMake 的核心这一节很重要因为它决定了你后面写 CMake 的风格。add_executable创建了一个 targethello。从此以后头文件目录挂到hello上宏定义挂到hello上编译选项挂到hello上依赖的库挂到hello上所有内容都围绕 target 转这是现代 CMake 的核心思维。与之相对的是“旧式 CMake”# 旧式到处用全局变量 include_directories(include) add_compile_options(-Wall) add_definitions(USE_FANCY_LOG1) add_executable(hello main.cpp) target_link_libraries(hello some_lib)这种写法会让所有 target 都共享这些设置互相污染所以现代 CMake 强烈不推荐。记住一句话target 是第一公民全局命令少用。把最小工程再压缩到极致如果只允许保留三行下面这三行就是一个最小 CMake 工程cmake_minimum_required(VERSION 3.15) project(Min LANGUAGES CXX) add_executable(min main.cpp)跑一下cmake-S.-Bbuild-GNinja cmake--buildbuild ./build/min你会发现CMake 工程其实没有想象中复杂。这一篇抓什么这一篇真正要带走的是这几点CMakeLists.txt最小有三行cmake_minimum_required、project、add_executablecmake_minimum_required是兼容性护栏必须写在最前面project命名项目并设置语言和可选版本号add_executable创建 target是现代 CMake 的起点target 是 CMake 的第一公民所有构建属性最终挂到 target 上只要这三行命令的作用清楚了后面学target_include_directories、target_link_libraries就非常自然。总结第一个 CMake 项目这一篇真正要抓住的是这一组主线cmake_minimum_required设定最低版本project命名项目、声明语言add_executable创建可执行文件 targettarget 是 CMake 一切行为的起点最小工程只需要这三行下一篇会讲源码目录、构建目录与 out-of-source build把“源码”和“构建产物”为什么要分开这件事说清楚。⚠️ 写在最后以上内容是我在学习以后得一些总结和概括如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流