构建与发布说明

July 11, 2026 · View on GitHub

1. 构建入口

当前仓库主要构建入口如下:

入口作用
build.ps1本地完整发布入口,默认构建 installer 和 portable 产物。
build-release.bat调用 build.ps1 -Configuration Release 的批处理入口。
build-beta.bat调用 build.ps1 -Configuration Beta 的批处理入口。
Scripts/Build-Target.ps1GitHub Actions 和单目标构建使用的入口。
Scripts/Build.Common.psm1restore、publish、打包、ProbeHost 多架构映射、Inno Setup 调用等公共函数。
NuGet.Configrestore 使用的 NuGet 配置。

当前代码库没有发现名为 build.bat 的入口;不要在文档或自动化中假设它存在。

主开发入口是根目录 ContextMenuMgr.slnx。该 slnx 同时包含 Frontend、Backend、TrayHost、Contracts 和 native C++ ContextMenuMgr.ProbeHost.vcxproj;不需要打开单独的 ProbeHost-only solution 才能看到 ProbeHost 项目。前端项目和发布脚本仍会自动构建、复制 native ProbeHost 多架构产物。

2. 获取源码与 Submodule 要求

本仓库包含 Git submodule。首次获取源码时建议使用 recursive clone:

git clone --recursive https://github.com/PLFJY/ContextMenuMgr.git

如果已经使用普通 git clone 获取仓库,请在仓库根目录执行:

git submodule update --init --recursive

未初始化 submodule 时,可能出现以下问题:

  • 仓库内置的 Inno Setup 编译器缺失,例如 Installer\Inno Setup 6\ISCC.exe 不存在;
  • 安装包构建失败;
  • 未来 native ProbeHost 或其它第三方源码依赖缺失;
  • CI / 本地构建表现不一致。

因此,任何涉及安装包、内置构建工具、native helper 或第三方源码依赖的任务,都应先确认 submodule 已初始化。Agent 不应假设普通 clone 已经包含完整构建依赖。

3. 项目产物

项目输出说明
FrontendContextMenuManagerPlus.exeWPF UI,最终发布目录的主程序。
Backend ServiceContextMenuManagerPlus.Service.exeContextMenuManagerPlus.Service.dllWindows Service / bootstrapper 相关后端程序。
TrayHostContextMenuManagerPlus.TrayHost.exe每用户托盘进程。
ProbeHostProbeHost\<arch>\ContextMenuMgr.ProbeHost.exeDeep Analysis 多架构隔离进程。
ContractsContextMenuMgr.Contracts.dllpipe 契约、模型、共享路径常量。ProbeHost 已改为 native C++,不再运行时依赖 Contracts DLL。

4. Debug 本地构建

ContextMenuMgr.Frontend.csproj 在普通 framework-dependent build 中会负责准备运行所需辅助产物。Debug 本地开发默认只构建 Win32,x64 ProbeHost,避免未安装 ARM64 C++ 工具链的 x64 开发机无法 dotnet run;Release / Beta 和发布构建仍默认构建 Win32,x64,ARM64。portable anycpu 发布的最终 ProbeHost 校验会沿用同一组标签:Debug 校验 x86 / x64,非 Debug 校验 x86 / x64 / arm64。

Frontend build
-> 使用 MSBuild 构建 native C++ ProbeHost(Debug 默认 Win32 / x64;Release / Beta 默认 Win32 / x64 / ARM64)
-> 复制 Backend artifacts
-> 复制 TrayHost artifacts
-> 复制已构建 ProbeHost 架构目录下的 ContextMenuMgr.ProbeHost.exe
-> 复制 ThirdPartyNotices\nlohmann-json-LICENSE.MIT
-> 执行 Verify-ProbeHostArchitecture.ps1

ProbeHost 是单文件 native exe。前端项目不再检查 ContextMenuMgr.ProbeHost.dll.deps.json.runtimeconfig.json 或 ProbeHost 目录中的 ContextMenuMgr.Contracts.dll。本地构建要求安装 Visual Studio Build Tools C++ workload、Windows SDK;如果要构建 arm64 ProbeHost,还需要 ARM64 工具链。已安装 ARM64 工具链的开发机可用 -p:NativeProbeHostPlatforms=Win32,x64,ARM64 强制 Debug 也构建三架构。

普通 dotnet build / dotnet run 不会无条件清理 native ProbeHost 输出。ContextMenuMgr.Frontend.csproj 会先用 MSBuild incremental build 检查 ContextMenuMgr.ProbeHost.vcxprojsrc\**\*.cppsrc\**\*.hthird_party\nlohmann\json.hpp 是否晚于当前 NativeProbeHostPlatforms 对应架构的目标 exe;全部最新时,BuildNativeProbeHostArtifacts target 会直接跳过,不启动 PowerShell。

如果 target 需要运行,Scripts\Build-NativeProbeHostArtifacts.ps1 会在一次 Windows PowerShell 5.1 兼容的 PowerShell 进程中处理 NativeProbeHostPlatforms 指定的平台,并且只解析一次 MSBuild.exe 路径。脚本仍会对每个架构单独检查目标 ContextMenuMgr.ProbeHost.exe 是否最新;如果某个架构已是最新,会输出该 label 的 Skipped=True 并跳过该架构的 MSBuild。需要强制重建时可传入:

dotnet build .\ContextMenuMgr.Frontend\ContextMenuMgr.Frontend.csproj -p:ForceRebuildNativeProbeHost=true

dotnet clean / dotnet rebuild 会清理 native ProbeHost 输出和 obj 目录。

本地 native ProbeHost 输出和中间目录按架构隔离,不共用 OutDirIntDir

artifacts\probehost-native\<Configuration>\x86\
artifacts\probehost-native\<Configuration>\x64\
artifacts\probehost-native\<Configuration>\arm64\
artifacts\probehost-native\<Configuration>\obj\x86\
artifacts\probehost-native\<Configuration>\obj\x64\
artifacts\probehost-native\<Configuration>\obj\arm64\

Build-NativeProbeHostArtifacts.ps1 会把 MSBuild Platform 映射到 label:Win32 -> x86x64 -> x64ARM64 -> arm64。每个 label 构建或增量跳过后都会立即读取目标 exe 的 PE Machine 并验证:x86 -> 0x014Cx64 -> 0x8664arm64 -> 0xAA64Build-NativeProbeHost.ps1 保留为单架构 helper。

5. Release 发布

build.ps1 负责组合多个平台和分发模式。Scripts/Build.Common.psm1 中的关键步骤包括:

build.ps1 会按 MaxParallel 并行启动多个 Scripts/Build-Target.ps1 子进程。任一目标失败后会停止继续调度,并对仍在运行的目标取 .ToArray() 快照后逐个停止,避免在 PowerShell 5.1 中直接用 @(...) 包装 generic list 时触发类型转换异常。

并行发布目标会给 dotnet restore/publish 传入目标专属的 BaseOutputPath--artifacts-path。辅助项目的 OutputPath 必须从 $(BaseOutputPath) 派生;不要硬编码回仓库级共享目录,否则 x86/x64/arm64 同时发布时可能抢写同一个 .runtimeconfig.json.deps.json 或 apphost。

步骤说明
restore对 Frontend、Backend、TrayHost 运行 dotnet restore,使用 NuGet.Config。ProbeHost 是 native C++ 项目,不走 dotnet restore。
publish对项目运行 dotnet publish,按平台和分发模式输出到 publish workspace。
self-contained发布时携带 .NET runtime,安装包不依赖本机已安装 runtime。
framework-dependent依赖本机 .NET runtime,安装包可启用 .NET dependency installer。
installer调用 Inno Setup 生成安装包。
portable当前支持 framework-dependent + anycpu 的 portable zip。
artifacts.txtbuild.ps1build/dist 下写入产物清单。

发布目录必须包含 ContextMenuMgr.package.json 运行时包类型标记。Installer 发布目录写入 { "packageKind": "Installer" },Portable 发布目录写入 { "packageKind": "Portable" }。运行时代码只读取 AppContext.BaseDirectory 下的该文件;缺失或无效时按 Installer 处理,不根据路径猜测包类型。

Build-Target.ps1 对 installer 目标要求平台是 win-x64win-x86win-arm64;portable 当前只支持 framework-dependent anycpu

6. ProbeHost 多架构

Get-ProbeHostArchitectureMap 定义发布包应携带的 ProbeHost 架构:

发布平台ProbeHost 架构
win-x86x86
win-x64x64、x86
win-arm64arm64、x64、x86

这样做是因为目标 Shell Extension DLL 的架构可能与主程序平台不同。例如 x64 系统仍可能安装 x86 handler,arm64 系统也可能需要 x64 或 x86 ProbeHost。

常见错误包括:目录标签和 PE 架构不一致、只发布主架构缺少 x86 fallback、机器缺少 C++ build tools 或 ARM64 工具链。

当前 Release 脚本使用 MSBuild 构建 ContextMenuMgr.ProbeHost.vcxproj,按 Win32x64ARM64 平台生成单个 ContextMenuMgr.ProbeHost.exe,再复制到 ProbeHost\<arch>。不再对 ProbeHost 运行 dotnet publish,也不会发布 .dll.deps.json.runtimeconfig.json 或 ProbeHost 目录内的 ContextMenuMgr.Contracts.dll

构建脚本在调用 native MSBuild 前会先检查目标架构的 cl.exe 是否存在,并用规范化后的进程环境启动 MSBuild,避免当前 shell 同时带有 Path / PATH 两个环境变量时触发 C++ ToolTask 的 Item has already been added 异常。该外部命令启动逻辑必须兼容 Windows PowerShell 5.1,因为前端 csproj 的 ProbeHost target 通过 powershell.exe 调用脚本;不能依赖仅 PowerShell 7 / newer .NET 才有的 ProcessStartInfo.ArgumentList API。缺少 ARM64 C++ build tools 时,发布构建会在 ProbeHost arm64 阶段直接报出缺失工具链;需要通过 Visual Studio Installer 安装 C++ ARM64 build tools 和 Windows SDK 后再重试。

Release 构建同样按发布目标和 architecture label 隔离 native ProbeHost 中间目录。例如 installer\self-contained\win-x64 的 native obj 位于:

build\publish\_artifacts\installer\self-contained\win-x64\probehost-native-obj\x64\
build\publish\_artifacts\installer\self-contained\win-x64\probehost-native-obj\x86\

发布输出仍位于最终发布目录下的 ProbeHost\<arch>\ContextMenuMgr.ProbeHost.exe。每个 label 的 MSBuild 构建完成后会立即验证该 exe 的 PE Machine,随后仍执行 Verify-ProbeHostArchitecture.ps1 -Root <ProbeHostRoot> -Labels ... 做整体发布目录校验。

ProbeHost 使用 vendored nlohmann/json 单头文件解析 request/result JSON:

  • 头文件位于 ContextMenuMgr.ProbeHost\third_party\nlohmann\json.hpp
  • 不使用 vcpkg、Conan、NuGet 或 Git submodule 获取 JSON;
  • json.hpp 是编译期依赖,不复制到 runtime output;
  • 发行产物必须包含 ThirdPartyNotices\nlohmann-json-LICENSE.MIT
  • ProbeHost exe 包含该 header-only 依赖编译后的代码。

Deep Analysis 菜单项图标编码使用 Windows 内置 WIC,native ProbeHost 链接 windowscodecs.lib。这不需要复制新的运行时文件,也不改变 ProbeHost 多架构输出布局。

7. Inno Setup

安装包脚本位于 Installer/build_Installer.iss。构建脚本会定位 ISCC.exe,优先检查仓库内 Installer\Inno Setup 6\ISCC.exe,然后检查 Program Files 和 PATH。

Invoke-InstallerBuildTarget 会向 Inno Setup 传入应用版本、AppId、发布目录、输出目录、架构选项和是否启用 .NET dependency installer。framework-dependent 安装包会把 MyUseDotNetDependencyInstaller 设为 1

self-contained installer 目标还会从同一个 installer publish 输出生成 portable zip。因为 installer publish 输出带 Installer 标记,脚本必须先复制到 staging 目录,再把 staging 中的 ContextMenuMgr.package.json 覆盖为 Portable,最后压缩该 staging 目录;不要直接压缩 installer publish 目录作为 portable 包。

8. GitHub Actions

.github/workflows/manual-release.yml 是当前手动发布 workflow。它通过 workflow_dispatch 接收 configurationapp_id,主要流程是:

resolve-metadata
-> Resolve-Version.ps1
-> Beta version gate(仅 Beta 配置,见下文)
-> New-ReleaseNotes.ps1
-> build matrix
-> Scripts/Build-Target.ps1
-> 上传 installer / portable artifacts
-> 汇总 artifacts
-> New-ChecksumTable.ps1
-> 创建 draft release

Beta version gate 步骤只在 configuration == Betashould_create_release == true 时执行,调用 Scripts/Test-BetaVersionGate.ps1 查询 GitHub API 获取最新已发布的非预发布 Release,比较当前 Beta 基础版本与最新 Stable 版本。如果 Beta 基础版本 ≤ Stable 版本,workflow 直接报错,阻止后续构建和 draft release 创建。该门禁的原因和包管理器版本策略见 包管理器发布说明

构建矩阵覆盖 win-x64win-x86win-arm64 的 self-contained 和 framework-dependent installer,以及 framework-dependent portable。

.github/workflows/publish-package-managers.yml 是包管理器发布 workflow。它在 GitHub Release 从 draft 被维护者手动发布后,通过 release.published 触发,读取已公开的 Release assets,生成并发布 Scoop / winget manifests。Scoop 和 winget 的详细渠道、变量、secret、dry-run 和首个 Beta 验证流程见 包管理器发布说明

9. 构建排错

问题优先检查
缺 .NET SDKdotnet --info,workflow 使用 10.0.x
Inno Setup 找不到Scripts/Build.Common.psm1Get-InnoSetupCompilerPath 搜索路径。
ProbeHost 缺失ContextMenuMgr.Frontend.csproj 的 native MSBuild 目标和发布目录内容。
ProbeHost 架构错Scripts/Verify-ProbeHostArchitecture.ps1 输出和 ProbeHost\<arch> 目录。
ARM64 installer 在 ProbeHost 阶段失败安装 Visual Studio C++ ARM64 build tools 和 Windows SDK;脚本会在调用 MSBuild 前检查 arm64\cl.exe
C++ ToolTask 报 Key in dictionary: 'Path' Key being added: 'PATH'使用当前构建脚本重试;native MSBuild 调用会传入规范化后的 PATH 环境。
artifacts 目录污染清理 build\publish-runsbuild\publishbuild\dist 后重试。
framework-dependent 包运行时缺失检查目标机器 .NET runtime 或安装包 dependency installer 设置。
portable 目标参数错误当前只支持 -Kind portable -Platform anycpu -DistributionMode framework-dependent