PowerShell 构建流程内幕解析:顶层工程、Dummy 依赖、ResGen 与类型目录生成机制
本文基于 PowerShell 仓库的 docs/building/internals.md 撰写,深入剖析 PowerShell 从源码构建时的关键内部机制:为何要在顶层工程中引入"假依赖"、ResGen 强类型资源类与 CoreCLR 类型目录(Type Catalog)这两个构建前置步骤的工作方式,以及 pwrshplugin.dll、libpsl-native 等原生组件如何通过 NuGet 包实现"一次构建、多次复用"。读完本文,你可以独立手工执行 Start-PSBuild 背后的每一步构建前置动作,并能定位"missing strings"、InitializeTypeCatalog does not exist 等典型构建错误。
需要说明的前提:该文档自述"并非完整,最终事实来源是在对应 CI 系统上实际执行的 build.psm1"。本文所有函数名、命令均以当前仓库中的构建脚本与工程文件为准。
一、构建入口:只构建一个"$Top"顶层工程
PowerShell 的构建策略是:对仓库中的顶层目录执行一次 dotnet 构建,由该目录汇总拉起整个 CoreCLR 版本的所有程序集。文档明确指出顶层目录(文中称 $Top)为:
src/powershell-win-core:Windows 上 CoreCLR 版本的顶层工程;src/powershell-unix:Linux 与 macOS 上 CoreCLR 版本的顶层工程。
两个工程都复用同一个 C# 入口 Program.cs,输出程序集名均为 pwsh。可以从 powershell-win-core.csproj 看到 Windows 版声明了 win-x86;win-x64 两个 RuntimeIdentifier,并额外打包了 Windows 专属的 Modules 内容(..\Modules\Windows\**、WSMan/诊断相关 csproj 等);而 powershell-unix.csproj 声明的是 linux-x64;osx-x64,内容项对应 ..\Modules\Unix\**。这印证了文档中"同一套核心、分平台顶层工程"的组织方式。
在 build.psm1 中,Start-PSBuild(约 L336 起)是对外的主构建函数,Start-PSBootstrap(约 L2848 起)负责首次安装构建前置依赖(.NET SDK 等),二者共同构成"一键构建"的入口。文档的前提也正是"你已能在本机成功从源码构建 PowerShell",internals 文档面向的是构建已经跑通、需要理解细节的读者。
二、Dummy Dependencies:让"只构建一个文件夹"成为可能
为什么要有假依赖
文档指出,各工程之间使用了 **dummy dependencies(假依赖)**来利用 dotnet 构建的功能:例如 src/powershell-win-core/powershell-win-core.csproj 声明了对 Microsoft.PowerShell.Commands.Diagnostics.csproj 的 ProjectReference,但实际上二者之间并不存在真正的构建期依赖。假依赖的作用是让构建系统沿着引用关系自动传递、编译所有相关工程——于是只需构建 $Top 一个目录,而不必逐个构建多个文件夹。
从源码结构看,这一点可以直接验证。powershell-win-core.csproj 的 ItemGroup 中引用了:
Microsoft.PowerShell.SDK.csprojMicrosoft.PowerShell.Commands.Diagnostics.csprojMicrosoft.Management.Infrastructure.CimCmdlets.csprojMicrosoft.WSMan.Management.csproj- (WindowsDesktop SDK 条件下)
Microsoft.PowerShell.GraphicalHost.csproj
而 powershell-unix.csproj 只引用了 Microsoft.PowerShell.SDK.csproj 一项——因为 Unix 构建不需要诊断、CimCmdlets、WSMan 等 Windows 专属组件。SDK 工程内部再把这些组件串成引用链,最终形成从 $Top 出发的完整构建闭包。
假依赖的规则
文档给出了维护这条依赖链的规则:凡是参与 CoreCLR 构建的程序集,都必须列为 $Top 文件夹(src/powershell-unix 或 src/powershell-win-core,按其所属平台)的依赖项。换言之,新增一个要编入发布的程序集时,不能只改 SDK 或某个中间工程的引用,还要确认它最终能被两个(或对应平台的)顶层工程"拉到"构建图中,否则该程序集不会出现在构建产物里。
三、构建前置步骤一:ResGen 生成强类型资源类
背景:为什么要自研 ResGen
在 .NET CLI 的 dotnet-resgen 工具支持生成强类型资源访问类之前(文档引用了上游 msbuild 的跟踪 issue),PowerShell 自行实现了一个 C# 工具,源码位于 src/ResGen。Start-PSBuild 会通过 build.psm1 中的 Start-ResGen 函数(L3292-L3313,内部就是切到 src/ResGen 后执行 dotnet run)自动执行这一步;但它不依赖 PowerShell 环境,可以完全手工运行:
cd src/ResGen
dotnet restore
dotnet run
工具做了什么
运行后,工具对每个项目执行以下工作(对应 Program.cs 的 Main 逻辑):
- 遍历
src下的各工程目录,凡存在resources文件夹的,创建同级gen文件夹; - 对
resources中的每个*.resx文件,解析其<data>节点,生成一个强类型 C# 资源访问类,写入gen文件夹下对应的*.cs文件。
从 Program.cs 的实现可以看到一些细节:resx 文件名默认生成 internal 访问级别的类;若文件名以 public. 前缀开头,则生成 public 类并去掉该前缀(例如 public.MyResource.resx → public class MyResource)。生成的类包含缓存的 ResourceManager 属性(资源名格式为 "{模块名}.resources.{类名}")以及每个字符串对应一个属性,属性内嵌 doc 注释给出原文提示。支持 dotnet run <path-to-resx> 单文件模式,也支持无参数的全量模式。
为什么不能每次构建都重跑
文档特别强调:这些 gen 文件不会在每次构建时自动更新,因为项目缺乏文件变更检测能力——每次构建都重新生成会破坏增量重编译。由此得到一个重要的运维结论:
如果你拉取新提交后出现"缺少字符串"(missing strings)错误,很可能需要删除
gen文件夹并重新运行 ResGen 工具。
这是"拉代码后编译失败"类问题中最常见的一种,排查时优先检查各工程的 gen 目录是否为陈旧版本。
四、构建前置步骤二:Type Catalog 生成
用途
PowerShell 在 CoreCLR 下使用一份预先生成的 C# 类型目录来辅助类型解析。其原理可以从 TypeCatalogGen.cs 的注释中得到印证:CoreCLR 中没有直接枚举"全部已加载 TPA 程序集"的 API,为了按类型名定位 .NET 类型,必须事先知道哪些 .NET 类型存在、分别位于哪个 TPA 程序集——因此构建期基于 .NET 的引用程序集生成一份"类型全名 → 程序集强名称"的目录,编译进程序集加载上下文初始化代码中。工具会遍历引用程序集中的 TypeDefinition,只收录 public/nested public 可见性的类型(TypeCatalogGen.cs)。
标准调用链与手工执行
生成类型目录是构建前置步骤,由 build.psm1 中的 Start-TypeGen(L3254-L3290)执行,并被 Start-PSBuild 调用。同样,它不要求 PowerShell 参与,可以完全手工复现,分两步:
第一步,在 src 目录下通过自定义 MSBuild 目标导出依赖 DLL 路径列表 powershell.inc:
targetFile="Microsoft.PowerShell.SDK/obj/Microsoft.PowerShell.SDK.csproj.TypeCatalog.targets"
cat > $targetFile <<-"EOF"
<Project>
<Target Name="_GetDependencies"
DependsOnTargets="ResolveAssemblyReferencesDesignTime">
<ItemGroup>
<_RefAssemblyPath Include="%(_ReferencesFromRAR.HintPath)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' "/>
</ItemGroup>
<WriteLinesToFile File="$(_DependencyFile)" Lines="@(_RefAssemblyPath)" Overwrite="true" />
</Target>
</Project>
EOF
dotnet msbuild Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj /t:_GetDependencies "/property:DesignTimeBuild=true;_DependencyFile=$(pwd)/TypeCatalogGen/powershell.inc" /nologo
注意该目标刻意排除了 Microsoft.Management.Infrastructure 包提供的引用——从源码结构看,这是因为它由原生组件包(见下文 libpsl/PSRP 相关包)按 RID 提供,不参与类型目录的解析输入。
第二步,运行 TypeCatalogGen 工具,以 powershell.inc 为输入生成 CorePsTypeCatalog.cs:
cd ../TypeCatalogGen
dotnet restore
dotnet run ../System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs powershell.inc
powershell.inc 包含 PowerShell 各依赖项 DLL 的解析后路径,作为 TypeCatalogGen 工具的输入;工具据此生成源码文件 CorePsTypeCatalog.cs,供 System.Management.Automation 工程编译(从 build.psm1 的实现看,Start-TypeGen 还支持通过 -IncFileName 参数改变 .inc 文件名,以支持 Windows 与 WSL 同时构建时的文件隔离)。
典型错误定位
文档给出了一个标志性错误:
The name 'InitializeTypeCatalog' does not exist in the current context
它意味着 CorePsTypeCatalog.cs 这个生成文件不存在,解决方法就是按上述步骤重新生成。由于 CorePsTypeCatalog.cs 属于生成产物而非签入文件,仓库中不会出现它,这一点与 ResGen 的 gen 目录性质相同:都是"构建前置步骤"的产物,缺失时按步骤重新生成即可。
五、原生组件:打包成 NuGet,一次构建多次复用
设计动机
文档"Native Components"一节说明了原生组件的依赖关系与包装理由:
- Windows 上,PowerShell Core 依赖 WinRM 插件
pwrshplugin.dll以提供基于 WinRM 的远程管理(remoting); - Linux/macOS 上,依赖
libpsl-native.so/libpsl-native.dylib二进制提供一些必要支持。
构建这些原生组件需要额外安装依赖(对不改原生代码的构建者来说是负担),而原生组件代码又很少变动,每次都随 Start-PSBuild 重新编译没有意义。因此决策是:把原生组件打包成 NuGet 包,改动时才重新构建,之后长期复用产物二进制。对应关系为:
| 原生组件 | NuGet 包 |
|---|---|
pwrshplugin.dll |
psrp.windows |
libpsl-native |
libpsl |
需要指出当前仓库状态的一个演进事实:src/libpsl-native/README.md 声明该目录下的原生代码正在迁移到独立的 PowerShell-native 仓库,后续 PR 应提交到新仓库。也就是说,文档中描述的原生构建流程代表该组件"在仓库内构建"的历史形态,当前仓库保留的 libpsl-native 目录已进入迁移过渡期。
Windows 包:PSRP.Windows 与 PowerShell.Core.Instrumentation
构建 pwrshplugin.dll 与 PowerShell.Core.Instrumentation.dll 需要安装 Visual Studio 2017,并运行 Start-PSBootstrap -BuildWindowsNative 安装前置依赖。文档明确列出需勾选的组件:
- VC++ 2017 v141 toolset (x86, x64)
- Visual C++ compilers and libraries for ARM
- Visual C++ compilers and libraries for ARM64
- Visual C++ tools for CMake
- Visual C++ ATL Support
- Windows 10 SDK (10.0.16299.0) for Desktop C++ (ARM and ARM64)
- Windows 10 SDK (10.0.16299.0) for Desktop C++ (x86 and x64)
同时需要安装 3.10.0 或更新版本、支持 VS2017 与 ARM64 生成器的 CMake。然后运行 Start-BuildNativeWindowsBinaries 构建二进制,例如构建 arm64 目标的 Release 版本:
Start-BuildNativeWindowsBinaries -Configuration Release -Arch x64_arm64
文档对架构矩阵的要点值得记录:
- 必须为所有受支持架构构建并测试:
x86、x64、x64_arm、x64_arm64; x64_arm/x64_arm64表示宿主机须为 x64,交叉编译到 ARM;- 多架构连续构建时务必加
-clean开关——cmake 会缓存上一次运行,不清洗会导致后续架构使用错误的编译器。
构建成功后,pwrshplugin.dll、其 PDB 与 powershell.core.instrumentation.dll 会被放到 src/powershell-win-core 下。
为 pwrshplugin.dll 创建新的 NuGet 包的完整流程:
- 获取
psrp.windows.nuspec:可以从 Windows 机器上~/.nuget/packages/psrp.windows(近期构建过 PowerShell 的话存在),或从 powershell-core NuGet feed 下载现有包获得; - 将其复制到空文件夹并更新
<version>元素; - 把本次构建产物复制进去,还原与现有包一致的文件布局:
\---runtimes
+---win-x64
| \---native
| pwrshplugin.dll
| pwrshplugin.pdb
|
+---win-x86
| \---native
| pwrshplugin.dll
| pwrshplugin.pdb
+---win-arm
| \---native
| pwrshplugin.dll
| pwrshplugin.pdb
\---win-arm64
\---native
pwrshplugin.dll
pwrshplugin.pdb
- 使用
authenticode dual证书为 DLL 签名,然后在runtimes的父目录(即psrp.windows.nuspec所在目录)执行nuget pack,并使用最新版 nuget.exe; - 将新 nupkg 发布到 powershell-core feed 的
psrp.windows包。
PowerShell.Core.Instrumentation.dll 的 NuGet 包以同样方式在独立目录中创建,仅 nuspec 不同:它签入在本仓库 src/PowerShell.Core.Instrumentation 目录下(该目录同时包含 ETW 清单 PowerShell.Core.Instrumentation.man 与 RegisterManifest.ps1),发布目标为 powershell-core feed 的 PowerShell.Core.Instrumentation 包。
libpsl 包:跨平台与 CentOS 7 可移植性约束
Unix/macOS 侧的前置与构建命令:
## 构建 linux-x64 或 macOS 目标
Start-BuildNativeUnixBinaries
## 构建 linux-arm 目标
Start-BuildNativeUnixBinaries -BuildLinuxArm
其中 linux-arm 需要先运行 Start-PSBootstrap -BuildLinuxArm 安装额外前置依赖,且当时只能在 Ubuntu 机器上构建;linux-x64 与 macOS 则只需 Start-PSBootstrap 的初始运行即可,无额外前置。
构建成功后,libpsl-native.so(macOS 上为 libpsl-native.dylib)会被放到 src/powershell-unix 下。
创建 libpsl NuGet 包的关键约束与步骤:
- 从现有
libpsl包获取libpsl.nuspec(本机~/.nuget/packages/libpsl,或从 powershell-core feed 下载现有包); - 关键可移植性约束:为了让
linux-x64的二进制可移植到所有 Linux 发行版,libpsl-native.so必须在 CentOS 7 上构建——原因是 .NET Core 的 Linux 原生二进制同样在 CentOS 7 上构建,以避免对更新版本 glibc 产生依赖; - 需要构建
linux-x64、linux-arm、osx三个目标各一份二进制,并放入与现有包一致的布局:
└── runtimes
├── linux-arm
│ └── native
│ └── libpsl-native.so
├── linux-x64
│ └── native
│ └── libpsl-native.so
└── osx
└── native
└── libpsl-native.dylib
- 在文件夹内执行
nuget pack .(可能需要最新版 nuget.exe)。
六、排错要点汇总
把文档中分散的故障信号汇总成一张速查表:
| 现象 | 根因 | 处置 |
|---|---|---|
| 拉取新提交后报"missing strings" | 强类型资源类 gen/*.cs 过期或缺失 |
删除相关工程的 gen 目录,重新执行 src/ResGen(dotnet run) |
The name 'InitializeTypeCatalog' does not exist in the current context |
生成文件 CorePsTypeCatalog.cs 不存在 |
按第四节两步手工流程重新生成(先产出 powershell.inc,再运行 TypeCatalogGen) |
| ARM 交叉编译产物异常 | cmake 缓存了上一架构的构建状态 | 多架构连续构建时始终使用 -clean 开关 |
| linux-x64 包在低版本发行版加载失败 | 二进制依赖了过新的 glibc | 确认 linux-x64 二进制是在 CentOS 7 上构建的 |
| 新增程序集未进入发布产物 | 未遵守 dummy dependencies 规则 | 将该程序集挂入 src/powershell-unix / src/powershell-win-core 依赖链 |
最后重申适用前提:本文命令与路径以当前仓库快照为准(顶层工程为 src/powershell-win-core 与 src/powershell-unix;构建脚本为 build.psm1)。原生组件部分描述的是组件签入仓库内的构建流程,当前 libpsl-native 代码已启动向独立 PowerShell-native 仓库的迁移,后续原生组件构建与发包请以该独立仓库的流程为准,本文对应章节仍可视为该机制的权威历史说明。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00