5、项目信息配置:使用 project() 设置项目名、版本号、语言,理解 PROJECT_SOURCE_DIR 等变量

说实话,很多新手写 CMakeLists.txt,上来就是 add_executabletarget_link_libraries,把 project() 当成一个摆设。我刚开始带团队的时候,就遇到过有人把项目名写错,结果生成的构建目录乱七八糟,找半天找不到目标文件。

嗯,这一讲我们就来聊聊 project() 这个命令。它不只是写个名字那么简单。

5.1 project() 的基本用法

先看一个最基础的写法:

cmake_minimum_required(VERSION 3.10)
project(MyApp)

这样写,CMake 就知道你的项目叫 MyApp。但说实话,只写一个名字太浪费了。我个人习惯把版本号和语言也带上:

project(MyApp
    VERSION 1.0.0
    LANGUAGES CXX C
)

这里 VERSION 指定了版本号,LANGUAGES 告诉 CMake 我们需要 C++ 和 C 编译器。你想想看,如果不指定语言,CMake 会默认检测 C 和 C++,但如果你只写 LANGUAGES C,那它就不会去找 C++ 编译器了。这在纯 C 项目里能省一点检测时间。

小提示:版本号格式推荐用 major.minor.patch,比如 2.3.1。CMake 会自动解析出 PROJECT_VERSION_MAJORPROJECT_VERSION_MINORPROJECT_VERSION_PATCH 这些变量。

5.2 版本号到底有什么用?

你可能觉得版本号就是个字符串。我在项目中遇到过一件事:有一次需要给生成的动态库加上版本后缀,比如 libfoo.so.1.0.0。如果没有在 project() 里设置版本号,你就得手动写一个变量存版本号,然后到处引用。

但如果你用了 project(MyLib VERSION 1.0.0),CMake 会自动生成这些变量:

变量名 示例值 说明
PROJECT_VERSION 1.0.0 完整版本号
PROJECT_VERSION_MAJOR 1 主版本号
PROJECT_VERSION_MINOR 0 次版本号
PROJECT_VERSION_PATCH 0 补丁号

这样你在写 set_target_properties 或者生成安装包时,直接引用这些变量就行,不用到处复制粘贴版本号。说白了,就是少犯错。

5.3 理解 PROJECT_SOURCE_DIR 等变量

调用 project() 之后,CMake 会自动设置几个非常重要的变量。我刚开始学 CMake 时,最困惑的就是这些路径变量。咱们一个一个说清楚。

核心变量一览:
  • PROJECT_SOURCE_DIR:当前项目源码的根目录。也就是包含 CMakeLists.txt 的那个目录。
  • PROJECT_BINARY_DIR:当前项目的构建目录。如果你在 build/ 目录下执行 cmake ..,那它就是 build/
  • PROJECT_NAME:你在 project() 里设置的名字。
  • CMAKE_SOURCE_DIR:顶层 CMakeLists.txt 所在的目录。注意,如果项目有子目录,CMAKE_SOURCE_DIR 始终指向最顶层,而 PROJECT_SOURCE_DIR 会随着 project() 的调用而变化。

举个例子你就明白了。假设你的项目结构是这样的:

/home/user/myproject/
├── CMakeLists.txt          # 这里调用了 project(MyProject)
├── src/
│   ├── CMakeLists.txt      # 这里又调用了 project(SubProject)
│   └── main.cpp
└── lib/
    └── CMakeLists.txt

在顶层 CMakeLists.txt 中:

  • PROJECT_SOURCE_DIR = /home/user/myproject
  • CMAKE_SOURCE_DIR = /home/user/myproject

src/CMakeLists.txt 中:

  • PROJECT_SOURCE_DIR = /home/user/myproject/src(因为这里又调了一次 project()
  • CMAKE_SOURCE_DIR = /home/user/myproject(始终不变)

我曾经在写一个大型项目时,在子目录里用了 CMAKE_SOURCE_DIR 来引用顶层资源文件,结果后来重构时把子目录移到了另一个仓库,代码就炸了。从那以后,我建议:尽量用 PROJECT_SOURCE_DIR,除非你明确知道自己在做什么

5.4 一个完整的示例

咱们写一个完整的 CMakeLists.txt,把上面这些知识点串起来:

cmake_minimum_required(VERSION 3.10)

project(Calculator
    VERSION 2.1.0
    LANGUAGES CXX
)

# 打印项目信息
message(STATUS "项目名称: ${PROJECT_NAME}")
message(STATUS "版本号: ${PROJECT_VERSION}")
message(STATUS "源码目录: ${PROJECT_SOURCE_DIR}")
message(STATUS "构建目录: ${PROJECT_BINARY_DIR}")

# 用版本号生成一个头文件
configure_file(
    ${PROJECT_SOURCE_DIR}/config.h.in
    ${PROJECT_BINARY_DIR}/config.h
)

add_executable(${PROJECT_NAME} main.cpp)
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_BINARY_DIR})

对应的 config.h.in 文件:

#define APP_NAME "${PROJECT_NAME}"
#define APP_VERSION "${PROJECT_VERSION}"
#define APP_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}
#define APP_VERSION_MINOR ${PROJECT_VERSION_MINOR}
#define APP_VERSION_PATCH ${PROJECT_VERSION_PATCH}

这样编译出来的程序,就能直接读取版本号了。你想想看,以后改版本号只需要改 project() 那一行,所有地方自动更新,多省心。

5.5 知识体系图

下面这张图帮你理清 project() 相关的核心逻辑:

project() 命令 项目名 (MyApp) 版本号 (VERSION) 语言 (LANGUAGES) PROJECT_VERSION VERSION_MAJOR VERSION_MINOR VERSION_PATCH PROJECT_SOURCE_DIR PROJECT_BINARY_DIR C / CXX / Fortran 项目信息 → 变量自动生成 → 代码中引用

5.6 避坑指南

我曾经踩过的坑:
  • 不要在子目录随意调用 project():除非你明确需要创建一个独立的子项目(比如一个静态库单独发布),否则在子目录里调用 project() 会改变 PROJECT_SOURCE_DIR 的值,导致路径引用混乱。
  • 版本号字符串不要带空格:比如 VERSION 1.0.0 beta 会报错。老老实实用 1.0.0-beta 或者 1.0.0
  • LANGUAGES 不写也能用,但建议显式声明:CMake 默认会检测 C 和 C++,但如果你只写 LANGUAGES CXX,可以避免不必要的编译器检测,稍微加快配置速度。

好了,这一讲的内容就到这里。记住一句话:用好 project(),你的 CMakeLists.txt 就成功了一半。版本号、路径、项目名,这些基础信息一旦写对,后面写代码就顺了。


公众号:蓝海资料掘金营,微信 deep3321