在 CMake 中禁用彩色输出:NO_COLOR 环境变量完全指南
在 CMake 中禁用彩色输出:NO_COLOR 环境变量完全指南
导读:NO_COLOR 是一种在命令行工具生态中被广泛接受的通用约定——只要设置了它(且值非空、非 0),工具即使在连接终端的情况下也不会输出彩色消息。本文以 NO_COLOR 环境变量文档 为骨架,结合 CMake 源码中 cmStdIoTerminal.cxx 的实际实现,完整讲解 NO_COLOR 的语义、与 CLICOLOR/CLICOLOR_FORCE 的优先级关系、在 CMake 命令行工具中的底层生效机制,以及与 CMAKE_COLOR_DIAGNOSTICS 变量的分工,帮助你在 CI、日志归档、终端兼容等场景下彻底掌控 CMake 的彩色输出行为。
NO_COLOR 是什么
NO_COLOR 是 CMake 的一个环境变量,其初始值取自调用进程的环境(即启动 CMake 的 shell 或父进程所设置的值)。它于 CMake 4.1 版本引入(见 Help/envvar/NO_COLOR.rst 中的 .. versionadded:: 4.1)。
该变量遵循命令行工具领域的通用色彩约定(common convention,详见 bixense.com/clicolors 的存档页面):
当
NO_COLOR被设置为非空且不等于0的值时,命令行工具即使连接着终端,也不应打印彩色消息。
也就是说,激活条件非常宽松:只要 NO_COLOR 被设置为任意非空、非 "0" 的值(例如 1、true、yes,甚至任意字符串)即视为生效。空字符串或 0 则视为未激活。
与 CLICOLOR / CLICOLOR_FORCE 的优先级
CMake 共提供三个环境变量共同决定命令行工具的着色行为,NO_COLOR 在其中拥有最高优先级:
| 环境变量 | 引入版本 | 激活条件 | 作用 |
|---|---|---|---|
NO_COLOR |
4.1 | 非空且非 "0" |
禁用彩色输出,即使连接终端 |
CLICOLOR_FORCE |
3.5 | 非空且非 "0" |
强制彩色输出,即使未连接终端 |
CLICOLOR |
3.21 | 值等于 "0" |
禁用彩色输出(仅当连接终端时有意义) |
优先级规则(分别见 NO_COLOR.rst、CLICOLOR_FORCE.rst、CLICOLOR.rst):
- 若
NO_COLOR被激活,则它同时优先于CLICOLOR_FORCE和CLICOLOR——即使你同时设置了CLICOLOR_FORCE想强制出彩色,也会被 NO_COLOR 压制; - 否则,若
CLICOLOR_FORCE被激活,则它优先于CLICOLOR; CLICOLOR只有在三者中唯一被设置时才起作用,且仅当其值为"0"时才会禁用颜色。
源码级验证:TermEnv 的计算顺序
CMake 源码 cmStdIoTerminal.cxx 中的 TermEnv lambda 精确实现了上述优先级。它是一个在进程启动时只计算一次的惰性常量,按以下顺序检查:
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; // NO_COLOR 激活 → 直接关闭颜色
}
}
/* Force color ... */
if (cm::optional<std::string> cliColorForce =
cmSystemTools::GetEnvVar("CLICOLOR_FORCE")) {
if (!cliColorForce->empty() && *cliColorForce != "0"_s) {
return TermKind::VT100; // CLICOLOR_FORCE 激活 → 强制 VT100 颜色
}
}
/* Disable color ... */
if (cm::optional<std::string> cliColor =
cmSystemTools::GetEnvVar("CLICOLOR")) {
if (*cliColor == "0"_s) {
return TermKind::None; // CLICOLOR=0 → 关闭颜色
}
}
/* 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 检查排在最前:一旦命中非空且非
"0"的值,函数立刻返回TermKind::None,后续的CLICOLOR_FORCE、CLICOLOR、MAKE_TERMOUT分支全部不再执行——这正是"NO_COLOR 优先于 CLICOLOR_FORCE/CLICOLOR"的底层实现; - 返回值语义:
TermKind::None表示输出时不再附加任何 VT100 转义序列;TermKind::VT100表示输出带 ANSI 颜色的文本;返回cm::nullopt则回落到对输出流本身的判断(是否连接终端)。
各分支的激活判定细节
NO_COLOR:先通过GetEnvVar判断变量是否存在,再检查"非空"且"不等于"0""。注意"0"_s是 cm::string_view 字面量,比较的是字符串内容"0",因此设置NO_COLOR=0或NO_COLOR=(空)都不会关闭颜色;CLICOLOR_FORCE:同样要求非空且非"0"才强制启用颜色,返回TermKind::VT100,与是否连接终端无关;CLICOLOR:仅在值恰好为"0"时禁用颜色,是三者中判定最严格的一个;MAKE_TERMOUT:额外兼容 GNU make 4.1+ 的约定——当 make 告知其输出目标是 TTY 时(该变量非空),也强制使用 VT100 颜色。这保证了经由 make 驱动 CMake 的构建场景下颜色行为与终端一致。
在实际输出中的生效过程
TermEnv 计算出的结果最终通过 cmStdIoTerminal.cxx 的 Print 函数作用于所有命令行输出:
void Print(OStream& os, TermAttrSet const& attrs,
std::function<void(std::ostream&)> const& f)
{
TermKind kind = TermEnv ? *TermEnv : os.Kind();
switch (kind) {
case TermKind::None:
f(os.IOS()); // 直接输出原文,不带任何颜色转义
break;
case TermKind::VT100:
if (!attrs.empty()) {
SetVT100Attrs(os.IOS(), attrs); // 输出 VT100 转义前缀
f(os.IOS());
SetVT100Attrs(os.IOS(), TermAttr::Normal); // 复位为正常样式
} else {
f(os.IOS());
}
break;
#ifdef _WIN32
case TermKind::Console: { ... } // Windows 控制台专用路径
#endif
};
}
关键点:
- 若
TermEnv返回了确定值(例如TermKind::None),它优先于输出流自身的终端判定os.Kind();只有返回cm::nullopt时才回落到"是否连接终端"的自动检测; TermKind::None分支直接调用f(os.IOS())输出纯文本,不写入任何 VT100 转义序列(如ESC[31m红色、ESC[1m加粗),从而在日志、管道、CI 控制台等场景下得到干净的输出;- 颜色属性通过预定义的 VT100 码表(cmStdIoTerminal.cxx)映射,包含 Normal、ForegroundBold、红/绿/黄/蓝/品红/青/白等前景色及部分背景色;
- Windows 平台另有
TermKind::Console分支,通过SetConsoleTextAttribute等 Win32 API 直接设置控制台文本属性,无需 ANSI 转义。
cm::StdIo::Print 被 CMake 的命令行输出链路广泛调用,例如 cmMessenger.cxx、cmakemain.cxx、cmcmd.cxx 以及 cmCTest.cxx 等,因此设置 NO_COLOR 会影响 cmake、ctest、cmake -E 等工具的彩色诊断与进度输出。
与 CMAKE_COLOR_DIAGNOSTICS 的分工
需要注意:NO_COLOR(以及 CLICOLOR 系列)控制的是 CMake 命令行工具自身的输出,而生成后的构建系统(Makefile/Ninja 中编译命令的彩色输出)则由变量 CMAKE_COLOR_DIAGNOSTICS 控制,两者是不同层面的事情:
CMAKE_COLOR_DIAGNOSTICS(CMake 3.24 引入)有三态:ON、OFF和未定义;它控制 Makefile 生成器的构建消息颜色,以及是否向 GNU/Clang 编译器传入-fcolor-diagnostics/-fno-color-diagnostics标志;- 它未定义时,Makefile 生成器会把
CMAKE_COLOR_MAKEFILE初始化为ON,编译器也不带任何颜色诊断标志; - 该变量同样支持通过同名环境变量
CMAKE_COLOR_DIAGNOSTICS设置(环境变量优先于未定义的缓存变量)。
因此,若你想在 CI 中完全关闭颜色,通常需要双管齐下:设置 NO_COLOR 关掉 cmake/ctest 自身的彩色输出,并配合 CMAKE_COLOR_DIAGNOSTICS=OFF(或让构建系统按各自方式禁用)来关掉构建时的编译诊断颜色。
实用建议与使用示例
一次性禁用当前命令的颜色
NO_COLOR=1 cmake --version
NO_COLOR=1 ctest --output-on-failure
NO_COLOR=1 cmake -E echo "hello" # 若输出带颜色,也会被禁用
在会话/CI 中全局禁用
export NO_COLOR=1 # 之后所有 cmake/ctest 命令均无色输出
unset NO_COLOR # 恢复默认行为
在 CI 系统(GitHub Actions、GitLab CI、Jenkins)或需要把输出归档为日志文件的场景中,普遍推荐导出 NO_COLOR=1,以避免日志中出现不可读的 ANSI 转义码。注意 NO_COLOR=(空)和 NO_COLOR=0 都不会生效。
与 CLICOLOR_FORCE 组合时的行为
export NO_COLOR=1
export CLICOLOR_FORCE=1 # 无效:NO_COLOR 优先,仍输出纯文本
由于 NO_COLOR 的判定在 TermEnv 中排在第一位,这一组合的最终结果一定是禁用颜色,不会出现"两者打架"的歧义。
适用范围说明
NO_COLOR生效范围是 CMake 4.1 及以上版本(依据文档.. versionadded:: 4.1);在更早版本中请改用CLICOLOR(3.21+,需设为0)或CLICOLOR_FORCE(3.5+)配合终端自动检测来控制颜色;- 它属于标准的 CMake 环境变量,初始值取自调用进程环境(见 Help/envvar/include/ENV_VAR.rst),即 shell 导出值或 CI 平台注入值;
- 该约定在众多命令行工具中通用(详见 bixense.com/clicolors 约定),因此在 CMake 之外,它也能作为你设计自有 CLI 工具时的参考规范。
参考文档与源码索引
- 官方文档:Help/envvar/NO_COLOR.rst、Help/envvar/CLICOLOR.rst、Help/envvar/CLICOLOR_FORCE.rst、Help/variable/CMAKE_COLOR_DIAGNOSTICS.rst
- 核心实现:Source/cmStdIoTerminal.cxx(环境变量解析
TermEnv、VT100 转义码表、Print输出函数) - 调用方示例:cmMessenger.cxx、cmakemain.cxx、cmcmd.cxx、cmCTest.cxx