首页
/ Windows Terminal(OpenConsole)特征开关机制:til::feature 的 XML 声明、优先级裁决与源码实现

Windows Terminal(OpenConsole)特征开关机制:til::feature 的 XML 声明、优先级裁决与源码实现

2026-09-05 14:39:36作者:舒璇辛Bertina

Windows Terminal 仓库(OpenConsole)使用一套名为 til::feature 的特征开关(feature flag)体系,来决定不同代码分支、不同产品品牌(Branding)下哪些功能应被编译进去或运行时启用。其特征声明集中在 src/features.xml,由构建系统自动生成预处理器宏与 C++ 访问接口。读完本文,你将理解该文档定义的完整 XML 结构、六级优先级裁决规则,以及从 XML 到生成头文件、再到源码消费的全链路实现细节。

一、机制总览:XML 声明,构建期裁决

按照 doc/feature_flags.md 的定义,特征开关由存放在 src/features.xml 的 XML 文档控制。这条链路的核心环节是:

  1. 声明:开发者在 src/features.xml 中用 <feature> 元素描述一个开关的名称、默认状态、分支/品牌例外;
  2. 生成build/rules/GenerateFeatureFlags.proj 在 MSBuild 构建时调用 tools/Generate-FeatureStagingHeader.ps1,结合当前 Git 分支与产品品牌(Branding)裁决出每个开关的最终状态;
  3. 消费:生成的头文件 TilFeatureStaging.h 被强制包含进所有 C++ 编译单元,源码中既可用 #if TIL_FEATURE_XXX_ENABLED 做编译期裁剪,也可用 Feature_XXX::IsEnabled() 做运行时判断。

这套机制解决的实际问题在仓库中随处可见:例如 conhost 主程序 src/host/exe/exemain.cpp#if TIL_FEATURE_LEGACYCONHOST_ENABLED 决定是否加载旧版 conhostv1.dll,而 Windows Terminal 应用 src/cascadia/TerminalApp/AppActionHandlers.cpp 在处理“打开草稿板”操作时用 Feature_ScratchpadPane::IsEnabled() 判断是否允许创建草稿板面板。

二、特征声明 XML 的完整结构

官方文档给出了标准的示例文档,一个 <featureStaging> 根节点下包含若干 <feature>

<?xml version="1.0" encoding="utf-8"?>
<featureStaging xmlns="http://microsoft.com/TilFeatureStaging-Schema.xsd">
    <feature>
        <!-- This will produce Feature_XYZ::IsEnabled() and TIL_FEATURE_XYZ_ENABLED (preprocessor) -->
        <name>Feature_XYZ</name>

        <description>Does a cool thing</description>

        <!-- GitHub deliverable number; optional -->
        <id>1234</id>

        <!-- Whether the feature defaults to enabled or disabled -->
        <stage>AlwaysEnabled|AlwaysDisabled</stage>

        <!-- Branch wildcards where the feature should be *DISABLED* -->
        <alwaysDisabledBranchTokens>
            <branchToken>branch/with/wildcard/*</branchToken>
            <!-- ... more branchTokens ... -->
        </alwaysDisabledBranchTokens>

        <!-- Just like alwaysDisabledBranchTokens, but for *ENABLING* the feature. -->
        <alwaysEnabledBranchTokens>
            <brandingToken>...</brandingToken>
        </alwaysEnabledBranchTokens>

        <!-- Brandings where the feature should be *DISABLED* -->
        <alwaysDisabledBrandingTokens>
            <!-- Valid brandings include Dev, Preview, Release, WindowsInbox -->
            <brandingToken>Release</brandingToken>
            <!-- ... more brandingTokens ... -->
        </alwaysDisabledBrandingTokens>

        <!-- Just like alwaysDisabledBrandingTokens, but for *ENABLING* the feature -->
        <alwaysEnabledBrandingTokens>
            <brandingToken>...</brandingToken>
        </alwaysEnabledBrandingTokens>

        <!-- Unequivocally disable this feature in Release -->
        <alwaysDisabledReleaseTokens />
    </feature>
</featureStaging>

各字段的语义如下(结合 tools/FeatureStagingSchema.xsd 的约束):

字段 必填 说明 XSD 约束
name 开关名称,同时决定生成的预处理器宏与 C++ 结构体名 必须匹配 ^Feature_[a-zA-Z0-9_]+
description 开关用途的文本描述 非空字符串
id 对应的 GitHub 交付项(deliverable)编号 正整数
stage 默认状态 仅允许 AlwaysEnabledAlwaysDisabled
alwaysDisabledBranchTokens 在这些分支(支持通配符)上强制禁用 branchToken 列表,同一 feature 内不可重复
alwaysEnabledBranchTokens 在这些分支上强制启用 同上
alwaysDisabledBrandingTokens 在这些品牌下强制禁用 brandingToken 列表,同一 feature 内不可重复
alwaysEnabledBrandingTokens 在这些品牌下强制启用 同上
alwaysDisabledReleaseTokens 空元素即表示“在 Release 中无条件禁用” 可为空

需要指出两处文档与实现的细微差异:

  • 文档注释列出的有效品牌为 Dev、Preview、Release、WindowsInbox,但 XSD 的 brandingType 实际允许五种取值:Dev|Canary|Preview|Release|WindowsInboxsrc/features.xmlFeature_ScratchpadPaneFeature_MarkdownPane 就启用了 Canary 品牌;
  • 文档示例中 alwaysEnabledBranchTokens / alwaysEnabledBrandingTokens 内部误写成了 <branchToken> 标签,按 XSD 定义,品牌列表内的子元素应为 <brandingToken>

XSD 还通过 xs:keyfeatureBranchOverridesKeyfeatureBrandingOverridesKey)约束同一个 feature 内的分支 token 与品牌 token 不得重复,防止声明歧义。

三、优先级规则:六级裁决顺序

原文档的 Notes 部分强调了一条关键规则:凡是使用 alwaysDisabledReleaseTokens 标记为 Release 禁用的特征,在 Release 构建中始终禁用,即使它来自一个本应被通配符启用的分支(“WindowsInbox 视为 Release 的一种”这一事实可由生成脚本印证,见下文)。

文档给出的完整优先级从高到低为:

  1. alwaysDisabledReleaseTokens(Release 无条件禁用)
  2. 启用型分支 token
  3. 禁用型分支 token —— 若多个分支 token 同时命中,最长匹配的 token 胜出(最具体者胜)
  4. 启用型品牌 token
  5. 禁用型品牌 token
  6. 特征的默认状态(stage

这套顺序在 tools/Generate-FeatureStagingHeader.ps1Resolve-FinalFeatureStage 函数中得到了逐字实现:

  • 第一步检查 $Branding -In @("Release", "WindowsInbox")DisabledReleaseToken 为真,直接返回 AlwaysDisabled
  • 第二步遍历所有分支 token,用 $Branch -Like $branchToken 做通配匹配,并只保留长度最长的匹配项;
  • 第三步查询品牌 token 字典;
  • 全部未命中才落到 stage 默认值。

另外两条实现层面的细节值得注意:

  • Feature 类的构造函数中(tools/Generate-FeatureStagingHeader.ps1),启用的分支/品牌 token 会后写入并覆盖禁用的同名 token(注释明确写着 “AlwaysEnabled branches win over AlwaysDisabled branches”),这与“启用型优先级高于禁用型”的规则一致;
  • DisabledReleaseToken 的判定是 $Null -Ne $entry.alwaysDisabledReleaseTokens,即只要 XML 中出现了空的 <alwaysDisabledReleaseTokens/> 元素就算命中。

四、生成脚本:从 XML 到头文件

tools/Generate-FeatureStagingHeader.ps1 的调用参数为:

参数 说明
-Path(位置参数 1,必填) 特征 XML 文件路径(即 src/features.xml
-Branding 取值 Dev、Canary、Preview、Release、WindowsInbox,默认 Dev
-BranchOverride 手动覆盖分支名;缺省时依次尝试 git branch --show-currentgit rev-parse --abbrev-ref HEAD,再失败则跳过分支校验
-OutputPath 指定后写入文件,否则将生成内容直接输出到 stdout

脚本先加载 XSD 并对 XML 做 Validate(不合法文档直接报错终止),随后把所有 feature 按名称排序,逐一裁决最终状态,最后生成两类产物:

// 编译期:每个开关一个宏
#define TIL_FEATURE_XYZ_ENABLED 1

// 运行时(C++):每个开关一个结构体
struct Feature_XYZ
{
    static constexpr bool IsEnabled() { return TIL_FEATURE_XYZ_ENABLED == 1; }
};

生成的头文件头部带有 “THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT IT” 声明,并且每个结构体上都挂了 __pragma(detect_mismatch("ODR_violation_..._mismatch", ...)),用于检测同一翻译单元集合内因头文件版本不一致导致的 ODR(单一定义规则)违规——这一点在脚本的 CODE GENERATION 段(tools/Generate-FeatureStagingHeader.ps1)中可见。

宏名规则是 TIL_ + 全大写特征名 + _ENABLEDPreprocessorName 方法,tools/Generate-FeatureStagingHeader.ps1),因此 Feature_XYZ 生成 TIL_FEATURE_XYZ_ENABLED

五、构建系统集成:谁在何时触发生成

5.1 Windows Terminal 侧(MSBuild)

build/rules/GenerateFeatureFlags.proj 是一个独立于解决方案的工程,完成三件事:

  1. 品牌推导:由 MSBuild 属性 WindowsTerminalBranding 映射出 _WTBrandingName(Canary / Preview / Release),未设置时回落到 Devbuild/rules/GenerateFeatureFlags.proj);
  2. 分支缓存_GenerateBranchAndBrandingCache 目标先用 git.exe rev-parse --abbrev-ref HEAD 取当前分支,与品牌名一起写入 branch_branding_cache.txt,作为 MSBuild 增量构建的输入依赖之一;
  3. 生成头文件_RunFeatureFlagScript 目标以 features.xml 与缓存文件为输入,运行 PowerShell 脚本,把 stdout 收集到 MSBuild 项后,用 WriteOnlyWhenDifferent="true" 写出到 bin\$(Configuration)\inc\TilFeatureStaging.h

这个设计刻意避免了“分支一变就全量重编”:正如工程内注释所述,只有当新生成头文件的内容确实变化时才会触发下游重编译。

头文件如何进入每个编译单元?由 src/common.build.post.propsForcedIncludeFiles 全局强制包含 $(SolutionDir)\bin\$(Configuration)\inc\TilFeatureStaging.h,因此源码中无需显式 #include#if TIL_FEATURE_...Feature_...::IsEnabled() 随处可用。

5.2 conhost 侧(WindowsInbox 品牌)

在面向 Windows 自带控制台(conhost/ConPTY)的构建中,src/staging/makefile.inc-Branding WindowsInbox -Branch $(BUILDBRANCH) 调用同一脚本,输出 TilFeatureStaging.h 到目标目录;src/staging/sources 声明该头文件是独立构建产物(TARGETTYPE=NOTARGET),src/project.inc 则通过 /FI(forced include)把它注入 conhost 各编译单元。这解释了为什么 features.xml 里有大量 WindowsInbox 品牌例外——同一份源码在“商店版 Windows Terminal”与“系统内置 conhost”两种身份下需要呈现不同的功能集合。

六、仓库当前的特征开关清单

src/features.xml 为准,仓库当前声明了如下开关(alwaysDisabledReleaseTokens 列以 ✓ 表示存在空元素):

特征名 默认 stage 品牌例外 说明
Feature_ReceiveIncomingHandoff 启用 WindowsInbox 禁用 conhost 可接收来自 OpenConsole 的会话移交
Feature_EditableUnfocusedAppearance 启用 Release 无条件禁用 设置 UI 中编辑非聚焦外观
Feature_AttemptHandoff 禁用 WindowsInbox 启用 conhost 尝试把会话交给 OpenConsole
Feature_ConhostAtlasEngineCustomShaders 启用 WindowsInbox 禁用 conhost 的 Atlas 渲染引擎支持自定义着色器
Feature_UseNumpadEventsForClipboardInput 禁用 WindowsInbox 启用 剪贴板转换/输入状态机改用 Numpad 事件(注释说明为降低 Windows 内兼容性风险而保留旧版 GetQuickCharWidth
Feature_LegacyConhost 禁用 WindowsInbox 启用 conhost 支持 ForceV2=false 并尝试加载 conhostv1.dll
Feature_AtlasEnginePresentFallback 禁用 Release、WindowsInbox 启用 Present1 失败时回退到 Present
Feature_AtlasEngineLoudErrors 启用 Release 无条件禁用 Atlas 引擎向消费者上报每次提交失败,仅非 Release 需要
Feature_NearbyFontLoading 启用 WindowsInbox 禁用 渲染时加载可执行文件同目录下的字体(conhost 避免遍历整个 system32)
Feature_AdjustIndistinguishableText 启用 WindowsInbox 禁用 前景色自动调整为更可见
Feature_ScrollbarMarks 启用 WindowsInbox 禁用 实验性滚动条标记
Feature_DynamicSSHProfiles(id 9031) 启用 Release 无条件禁用 OpenSSH 配置文件的动态 profile 生成
Feature_ShellCompletions(id 3121) 启用 Release 无条件禁用 供客户端请求显示建议列表的实验性转义序列
Feature_VtChecksumReport(id 14974) 启用 WindowsInbox 禁用 DECRQCRA 校验和上报(Terminal 内已有开关,conhost 无关闭途径故整体裁剪)
Feature_ScratchpadPane(id 997) 禁用 Dev、Canary 启用 草稿板面板,用于验证非终端面板
Feature_MarkdownPane(id 16495) 禁用 Dev、Canary 启用 Markdown 面板,验证 Markdown 解析
Feature_KeypadModeEnabled(id 16654) 禁用 Dev 启用 使 DECKPAM/DECKPNM 序列按预期工作
Feature_SaveSnippet(id 9971) 启用 Release 无条件禁用 保存片段(Save Snippet)
Feature_QuickFix(id 16599) 启用 Release 无条件禁用 快速修复(Quick Fix)菜单
Feature_DebugModeUI 启用 Release 无条件禁用 调试模式设置的 UI 入口
Feature_WarnOnInvalidSettingsMediaResources 启用 Release 无条件禁用 图标/背景图/着色器等媒体资源缺失时弹警告

从这张清单可以直观看到三种典型用法:实验性功能用“默认禁用 + Dev/Canary 品牌启用”(如 Feature_KeypadModeEnabled);商店版与系统内置版差异化用“品牌禁用 WindowsInbox”或“仅 WindowsInbox 启用”(如 Feature_LegacyConhost);已稳定但尚未进入正式发布的用 alwaysDisabledReleaseTokens 在 Release 中兜底禁用(如 Feature_QuickFix)。

七、源码中的两种消费方式

编译期裁剪——功能连同代码一起被排除出产物:

  • src/host/exe/exemain.cppTIL_FEATURE_RECEIVEINCOMINGHANDOFF_ENABLEDTIL_FEATURE_LEGACYCONHOST_ENABLED 宏下分支,决定进程是否具备接收移交、回退旧版 conhost 的能力;
  • src/server/IoDispatchers.cpp#if !TIL_FEATURE_ATTEMPTHANDOFF_ENABLED 区分是否执行移交路径;
  • src/renderer/atlas/BackendD3D.cppTIL_FEATURE_CONHOSTATLASENGINECUSTOMSHADERS_ENABLED 下编译自定义着色器支持。

运行时判断——代码保留在产物中,按构建期裁决结果决定是否生效:

// src/cascadia/TerminalApp/AppActionHandlers.cpp
void TerminalPage::_HandleOpenScratchpad(const IInspectable& sender,
                                         const ActionEventArgs& args)
{
    if (Feature_ScratchpadPane::IsEnabled())
    {
        const auto& scratchPane{ winrt::make_self<ScratchpadContent>() };
        ...

两者最终读的都是同一个生成宏,区别仅在于是否允许产物二进制本身携带这段代码。

八、给贡献者的实操指引:新增一个特征开关

按文档与构建链的约定,新增开关的完整步骤是:

  1. src/features.xml 中追加一个 <feature> 元素,nameFeature_ 开头(XSD 会强制校验命名模式),填好 descriptionstage,按需追加 <id>、分支/品牌 token 或空的 <alwaysDisabledReleaseTokens/>
  2. 在 C++ 代码中二选一消费:编译期用 #if TIL_FEATURE_XXX_ENABLED(特征名全大写、前缀 TIL_、后缀 _ENABLED),运行时用 Feature_XXX::IsEnabled();无需手动 include 头文件,因为 TilFeatureStaging.h 已被全局强制包含(见 src/common.build.post.props);
  3. 本地构建时 MSBuild 会自动运行生成脚本(build/rules/GenerateFeatureFlags.proj),当前 Git 分支与 WindowsTerminalBranding 属性决定你构建出的二进制里哪些开关生效;如需验证其他分支的效果,可手动运行脚本并传 -BranchOverride,例如:
powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -Command `
  "&'tools\Generate-FeatureStagingHeader.ps1' -Path 'src\features.xml' -Branding Dev -BranchOverride 'rel/1.0' | Out-File -Encoding UTF8 feature_flags_preview.h'"
  1. 提交前可用 XSD(tools/FeatureStagingSchema.xsd)自查 XML 合法性;脚本本身在加载时即执行 Validate,非法文档会直接报错中断生成。

需要注意的是,分支 token 是通配匹配而非精确匹配(PowerShell 的 -Like 语义),编写 branchToken 时应参考仓库实际的分支命名规范;并且“启用型 token 覆盖禁用型 token、最长匹配胜出”这两条规则意味着,当多个 token 都可能命中时,最终状态以优先级表中更靠前的那条为准。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384