CMake 的 CLICOLOR 环境变量:完整控制命令行彩色输出指南
CMake 的 CLICOLOR 环境变量:完整控制命令行彩色输出指南
CLICOLOR 是 CMake 3.21 起引入的终端颜色控制环境变量,用于告诉 CMake 命令行工具在连接到终端时是否输出彩色消息。本文以 Help/envvar/CLICOLOR.rst 为骨架,结合 CMake 源码中的实际实现(Source/cmStdIoTerminal.cxx 与 Source/cmStdIoStream.cxx),完整讲解 CLICOLOR 的取值语义、与其他颜色控制变量的优先级关系、底层判定逻辑,以及在实际构建和 CI 场景中的用法,帮助你彻底掌控 CMake 输出颜色的开关。
一、CLICOLOR 是什么
CLICOLOR 是一个 CMake 环境变量(Environment Variable),其初始值取自调用进程的环境。按照 Help/envvar/include/ENV_VAR.rst 的通用说明,它与普通 CMake 变量不同,不需要在 CMakeLists.txt 中 set(),而是直接继承自 shell 或启动 CMake 的进程。
它的语义非常简洁:
将 CLICOLOR 设置为
0,可以告诉命令行工具即使连接到了终端也不要打印彩色消息。
这一约定并非 CMake 独创,而是命令行工具圈的"通用惯例"(common convention):许多终端程序都遵守同一套规则——CLICOLOR=0 表示禁用颜色,CLICOLOR_FORCE 表示强制启用颜色。CMake 3.21 起遵循该惯例,将 CMake 自身的命令行工具输出(如 configure 时的诊断信息、警告、错误消息)纳入这一套统一管控。
二、三个颜色变量的职责与优先级
CLICOLOR 并不是孤立的。CMake 同时支持三个相关环境变量,文档中明确给出了优先级关系。以下是三个变量的对照总览:
| 环境变量 | 引入版本 | 生效值 | 作用 |
|---|---|---|---|
NO_COLOR |
4.1 | 非空且不等于 0 |
即使连接到终端也禁用彩色消息 |
CLICOLOR_FORCE |
3.5 | 非空且不等于 0 |
即使未连接到终端也强制启用彩色消息 |
CLICOLOR |
3.21 | 恰好为 0 |
连接到终端时禁用彩色消息 |
优先级规则(由高到低):
- NO_COLOR 一旦激活,优先于
CLICOLOR_FORCE和CLICOLOR两者; - 否则,CLICOLOR_FORCE 一旦激活,优先于
CLICOLOR; - 只有前两者都未激活时,CLICOLOR 的取值才生效。
也就是说,三者呈现"强制禁用 > 强制启用 > 默认禁用"的覆盖关系。更精确地说,CLICOLOR=0 只有在 NO_COLOR 未激活且 CLICOLOR_FORCE 未激活时才会真正让 CMake 关闭颜色。相关文档可分别参见 Help/envvar/NO_COLOR.rst 和 Help/envvar/CLICOLOR_FORCE.rst。
三、源码实现:优先级判定究竟如何落地
上述优先级并非只写在文档里,在 CMake 源码中有完全对应的实现。CMake 在 Source/cmStdIoTerminal.cxx 中用一个立即执行的 lambda 表达式 TermEnv 解析这些环境变量,判定结果以 cm::optional<TermKind> 的形式缓存(只在进程启动时计算一次):
auto const TermEnv = []() -> cm::optional<TermKind> {
/* Disable color according to https://bixense.com/clicolors/ convention. */
if (cm::optional<std::string> noColor =
cmSystemTools::GetEnvVar("NO_COLOR")) {
if (!noColor->empty() && *noColor != "0"_s) {
return TermKind::None;
}
}
/* Force color according to https://bixense.com/clicolors/ convention. */
if (cm::optional<std::string> cliColorForce =
cmSystemTools::GetEnvVar("CLICOLOR_FORCE")) {
if (!cliColorForce->empty() && *cliColorForce != "0"_s) {
return TermKind::VT100;
}
}
/* Disable color according to https://bixense.com/clicolors/ convention. */
if (cm::optional<std::string> cliColor =
cmSystemTools::GetEnvVar("CLICOLOR")) {
if (*cliColor == "0"_s) {
return TermKind::None;
}
}
/* GNU make 4.1+ may tell us that its output is destined for a TTY. */
if (cm::optional<std::string> makeTermOut =
cmSystemTools::GetEnvVar("MAKE_TERMOUT")) {
if (!makeTermOut->empty()) {
return TermKind::VT100;
}
}
return cm::nullopt;
}();
从源码结构可以明确推断出以下实现事实:
- 判定顺序严格固定:
NO_COLOR→CLICOLOR_FORCE→CLICOLOR→MAKE_TERMOUT,先命中者直接短路返回,这构成了文档所述优先级关系的底层保障。 - 取值判定细节:
NO_COLOR和CLICOLOR_FORCE要求"非空且不等于0"才激活;而CLICOLOR则只认恰好等于0才禁用——任何其他值(包括空值)都不会关闭颜色,这与CLICOLOR=0的语义完全吻合。 MAKE_TERMOUT的补充:如果以上三个变量都未命中,CMake 还会检查 GNU make 4.1+ 设置的MAKE_TERMOUT环境变量;非空即认为输出面向终端,强制以 VT100 模式输出颜色。这也是"CLICOLOR 未设置时彩色输出仍然可能出现"的一个重要来源。- 结果是
optional:当所有变量都未激活时返回cm::nullopt,表示"交给终端类型自动判定",此时由 Source/cmStdIoTerminal.cxx 的Print函数回退到os.Kind()获取的终端能力。
四、终端能力判定:CLICOLOR 之外的底层逻辑
当 CLICOLOR 等变量未介入时,CMake 输出是否彩色取决于输出流本身的 TermKind,这一判定在 Source/cmStdIoStream.cxx 的流构造函数中完成:
- 类 Unix 平台:CMake 使用
isatty()判断文件描述符是否指向终端,同时检查TERM环境变量是否为已知的 VT100 兼容终端名(如xterm系列、screen、tmux等),两者都满足才将输出流标记为TermKind::VT100。 - Windows 平台:通过
GetConsoleMode探测控制台句柄,若成功启用ENABLE_VIRTUAL_TERMINAL_PROCESSING(虚拟终端处理),则视为TermKind::VT100;否则回退为通过SetConsoleTextAttribute直接设置控制台属性的TermKind::Console模式。
颜色属性的具体实现位于 Source/cmStdIoTerminal.h:TermAttr 枚举定义了 19 种文本属性(Normal、前景 8 色、背景 8 色及加粗),并映射为 VT100 转义序列(如 \33<a href="https://link.gitcode.com/i/5fac0ef91539240519d3bcece59b9d20" target="_blank">31m 红色、\33[32m 绿色),见 [Source/cmStdIoTerminal.cxx。
由此可见,CLICOLOR=0 的实际效果是:在 Print 输出路径上,TermEnv 直接返回 TermKind::None,从而跳过 SetVT100Attrs 转义序列的写入(Source/cmStdIoTerminal.cxx),最终输出纯文本。
五、实践用法
1. 临时禁用 CMake 命令行的彩色输出
# 单条命令
CLICOLOR=0 cmake -S . -B build
# 或导出到当前 shell 会话
export CLICOLOR=0
cmake --build build
设置后,CMake 的 configure 诊断信息、警告、错误消息等命令行输出将不再包含 ANSI 颜色转义序列。
2. 强制启用颜色(管道/重定向场景)
当 CMake 输出被重定向到文件或通过管道传给其他工具时,默认不再着色;若希望保留颜色以便人工查看日志文件,可以使用 CLICOLOR_FORCE:
CLICOLOR_FORCE=1 cmake -S . -B build > configure.log
3. 强制禁用颜色的最稳妥做法
在 CI、日志采集或文本处理场景中,若同时存在其他工具设置的 CLICOLOR_FORCE,单独设置 CLICOLOR=0 可能无效——因为 CLICOLOR_FORCE 优先级更高。此时应使用最高优先级的 NO_COLOR:
NO_COLOR=1 CLICOLOR=0 cmake -S . -B build
4. 验证是否生效
CMake 提供的 Source/cmCTest.cxx 中的判定方式表明,CTest 同样复用 cm::StdIo::Out().Kind() 来判断输出流是否为 VT100 终端。你可以通过对比同一命令在设置变量前后的输出来验证:在支持彩色的终端中,CLICOLOR=0 cmake -S . -B build 应输出不带转义码的纯文本,例如重定向到文件后用 cat -v build.log | grep '\^\\[' 检查是否残留 ^[ 开头的 ANSI 序列。
六、与生成系统的颜色控制:CMAKE_COLOR_DIAGNOSTICS
需要特别区分的是:CLICOLOR 只影响 CMake 自身命令行工具的运行时输出。对于"生成出来的构建系统"(如 Makefile 或 Ninja 构建过程中的编译诊断颜色),则要使用 CMAKE_COLOR_DIAGNOSTICS 变量控制,详见 Help/variable/CMAKE_COLOR_DIAGNOSTICS.rst。
该变量有三种状态,控制面更广:
- 未定义:Makefile 生成器将
CMAKE_COLOR_MAKEFILE初始化为ON,GNU/Clang 编译器不带颜色诊断参数; - ON:Makefile 生成器默认产生彩色构建消息(可通过
CMAKE_COLOR_MAKEFILE=OFF显式关闭),GNU/Clang 编译器追加-fcolor-diagnostics; - OFF:Makefile 生成器默认不产生彩色构建消息(可通过
CMAKE_COLOR_MAKEFILE=ON显式开启),GNU/Clang 编译器追加-fno-color-diagnostics。
此外,如果设置了 CMAKE_COLOR_DIAGNOSTICS 对应的同名环境变量,其值也会被采用。因此一份完整的"关闭所有颜色"的配置,通常需要同时考虑 CLI 层(CLICOLOR=0)与构建系统层(-DCMAKE_COLOR_DIAGNOSTICS=OFF)。
七、小结
| 场景 | 推荐设置 |
|---|---|
| 仅在交互终端禁用 CMake 命令行颜色 | export CLICOLOR=0 |
| 重定向/管道时强制保留颜色 | CLICOLOR_FORCE=1 |
| 无条件禁用命令行颜色(最高优先级) | NO_COLOR=1 |
| 关闭生成系统的编译诊断颜色 | cmake -DCMAKE_COLOR_DIAGNOSTICS=OFF |
CLICOLOR 是 CMake 融入命令行工具通用颜色约定的一环:它以 0 为唯一"禁用"信号,并置于 NO_COLOR、CLICOLOR_FORCE 之后作为兜底。理解其取值语义与 Source/cmStdIoTerminal.cxx 中体现的优先级实现,再配合 CMAKE_COLOR_DIAGNOSTICS 区分"CLI 层"与"构建系统层"的颜色控制,即可在本地开发、日志采集、CI 流水线等不同环境中精确掌控 CMake 的彩色输出行为。