在 CMake 中禁用彩色输出:NO_COLOR 环境变量完全指南

原创2026-10-05 23:57:431,881 阅读
文章标签:构建工具开发工具CLI

在 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;
}();

从这段代码可以明确读出:

  1. NO_COLOR 检查排在最前:一旦命中非空且非 "0" 的值,函数立刻返回 TermKind::None,后续的 CLICOLOR_FORCE、CLICOLOR、MAKE_TERMOUT 分支全部不再执行——这正是"NO_COLOR 优先于 CLICOLOR_FORCE/CLICOLOR"的底层实现;
  2. 返回值语义: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 工具时的参考规范。

参考文档与源码索引

登录后查看全文
CMake