Aspire CLI 安装路线旁车(install-route sidecar)机制:一个 JSON 文件如何决定 CLI 的目录布局与状态位置

发布时间:2026/9/17 20:39:04
Aspire CLI 安装路线旁车(install-route sidecar)机制:一个 JSON 文件如何决定 CLI 的目录布局与状态位置
Aspire CLI 安装路线旁车install-route sidecar机制一个 JSON 文件如何决定 CLI 的目录布局与状态位置【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire在 Aspire 项目中aspireCLI 通过一个与可执行文件并排放置的.aspire-install.json旁车文件来识别自己是通过哪条安装路线Homebrew、WinGet、dotnet-tool、安装脚本、Nix 等分发的。旁车中的source字段决定了 bundle 解压目录的布局形状以及便携安装下 hive、缓存、日志所在的 Aspire home 位置。读完本文你能理解这套旁车契约的完整字段定义、各安装路线的作者职责谁在什么时候写这个文件、构建与运行时两侧的守护机制以及该设计如何解决共享归档偷带旁车导致的布局错乱问题——这是 Aspire CLI 多路线分发的核心基础设施。旁车文件契约File Contract.aspire-install.json与正在运行的aspire二进制位于同一目录即binaryDir/.aspire-install.json。安装路线负责写入必需的source字段并可以附带供IdentityResolver消费的可选身份字段{ source: route, channel: stable|staging|daily|local|pr-N, version: informational version, commit: source commit }身份字段channel/version/commit都是可选的这样旧安装器写入的旁车仍然有效。这一设计在源码中得到了印证InstallSidecarReader.cs 中的SidecarFields记录结构把Source、Channel、Version、Commit全部声明为可空类型并额外解析了nugetServiceIndexOverride和packages两个字段——非字符串值的身份字段会被静默解析为null避免一个损坏的身份字段污染整个旁车读取。旁车读取还有几个值得注意的工程细节见 InstallSidecarReader.csAOT 安全读取使用JsonDocument而非源生成 JSON以适配 NativeAOT 场景尺寸上限MaxSidecarBytes设为 64KB规范载荷本身约 30 字节例如{source:localhive}防止 PATH 上候选二进制旁被植入的巨型旁车在安装发现过程中强迫大内存分配永不抛异常文件缺失、为空、source无法识别时返回InstallSource.Unknown调用方将其视为legacy / 旁车之前的安装并回退到默认布局。aspire update --self的行为与旁车深度耦合它会原子性地更新既有旁车中的channel在替换并验证可执行文件之后同时保留source和与可执行文件身份无关的字段version和commit的旧值会被移除让解析器从替换后的二进制本身读取这些值而不是保留已被替换二进制的身份。另一个重要边界无旁车sidecar-less的遗留安装会保持无旁车状态因为自更新无法推断其原始安装路线。持久化所选 channel 在staging 下载中包含被刻意打上stable标记的 ship-candidate 二进制的场景下是必需的。source取值与安装路线对应表源码侧的权威定义在 InstallSource.csInstallSource枚举与InstallSourceExtensions中的 wire 字符串常量一一对应注释明确声明这些字符串与docs/specs/install-routes.md契约完全一致是安装脚本和BundleService.ComputeDefaultExtractDir共同消费的 kebab-case 字符串。source值安装路线brewHomebrew caskwingetWinGet portable manifestdotnet-tooldotnet tool install -g Aspire.Cliscriptget-aspire-cli.{sh,ps1}prget-aspire-cli-pr.{sh,ps1}localhivelocalhive.{sh,ps1}本地构建的开发安装nixNix package / flake旁车决定什么bundle 解压目录与 Aspire home旁车的source值在运行时驱动两个关键决策二者都不做路径形状探测布局是旁车source值的纯函数1. bundle 解压目录BundleService.ComputeDefaultExtractDirBundleService.ComputeDefaultExtractDir是布局选择的唯一真相源。其实现见 BundleService.cs 中的ComputeDefaultExtractDir方法约 L448-L487逻辑是一个静态的、无依赖的 switch 表达式这样在 DI 尚未装配完成的路径上也能调用winget/brew/dotnet-tool→binaryDirflat 布局bundle 解压在二进制旁边script/pr/localhive→Path.GetDirectoryName(binaryDir)bin 布局bundle 解压为bin/的兄弟目录nix→ 默认 Aspire home设置了ASPIRE_HOME时为ASPIRE_HOME否则为$HOME/.aspire因为 Nix 包的输出位于只读的 Nix store 之下旁车缺失、不可读、格式错误或source未知 → 同样回退到默认 Aspire home。回退策略的意图在源码注释中写得很明确无旁车二进制可能安装在任意位置包括只读的软件包存储因此在没有路线级旁车显式选择就地解压时默认使用用户自己拥有的 Aspire home避免在 CLI 二进制旁边尝试写入。2. Aspire homeCliPathHelper.GetAspireHomeDirectoryCliPathHelper.GetAspireHomeDirectory是 Aspire-home 选择的唯一真相源实现见 CliPathHelper.cs约 L44-L50。它读取同一个旁车但只对 Aspire 自己拥有的便携路线改变 home让 hive、缓存、日志和 SDK 状态跟随安装位置script和localhive→bin的父目录pr→ 额外的特判TryGetPrInstallPrefix约 L133-L149沿bin→pr-N→dogfood逐级上溯且会校验目录名确实是dogfood常量来自InstallationDiscoveryLayout.DogfoodDirectoryName返回dogfood/pr-N/bin的祖父目录包管理器路线含 Nix以及无旁车二进制 → 保持默认 Aspire homeASPIRE_HOME或$HOME/.aspire因为它们的安装根目录归包管理器所有。两条读取路径都先经过ResolveSymlinkOrOriginalPath解析符号链接保证通过链接调用时仍能找到二进制真实位置旁的旁车。覆盖上述行为的测试位于 BundleServiceCrossRouteExtractionTests.cs作为 source × prefix-shape 组合行的 theory并包含brew 旁车落在 script 风格 prefix 下的跨路线用例和 CliPathHelperTests.cs。各路线的作者职责Per-route Authorship共享的 per-RID CLI 归档aspire-cli-rid-*.zip/.tar.gz出厂时不带旁车。这些归档在 brew、winget、release 脚本和 PR 脚本之间复用没有任何一方拥有路线标签因此每条路线在自己的安装时刻写入自己的旁车路线归档形态旁车写入者brew共享 per-RID tarballcask 的postflight块aspire.rb.templatewinget共享 per-RID zipCLI 首跑探针WingetFirstRunProbe.cs——利用 WinGet portable 的 ARP 注册表条目确认运行中的二进制确由 winget 放置然后盖写旁车script共享 per-RID archiveget-aspire-cli.{sh,ps1}解压后写入PR 脚本共享 per-RID archiveget-aspire-cli-pr.{sh,ps1}解压后写入dotnet-tool路线独占的 nupkg内嵌于 payload由 Aspire.Cli.csproj 的_PreparePreBuiltCliBinaryForPackTool目标暂存localhive仅本地无共享归档localhive.{sh,ps1} 在把 CLI 二进制拷入prefix/bin/后写入旁车使用--output PATH时旁车写在输出目录内——由于 localhive 归档是路线独占的这是合理的nix共享 per-RID archiveNix derivation 在$out/lib/aspire-cli下打包的原生二进制旁写入旁车仓库中可以逐条核验这些写入点brewaspire.rb.template 的postflight_steps中write_file .aspire-install.json, {\source\:\brew\}\nscriptget-aspire-cli.ps1 的Write-InstallSidecar函数写出{source:script}若指定了 quality 则附带 channel{source:script,channel:channel}且写入采用临时文件 备份的原子化方式shell 侧的write_install_sidecarget-aspire-cli.sh同理并支持--dry-run只打印不写盘PR 脚本get-aspire-cli-pr.sh 明确写出旁车落在install_prefix/dogfood/pr-N/bin/.aspire-install.json并声明如果未来或外部归档在同一位置偷带旁车本次写入有意覆盖它PowerShell 侧 get-aspire-cli-pr.ps1 在-WhatIf下只打印目标路径而不覆盖真实用户旁车localhivelocalhive.sh 直接printf %s {source:localhive} $CLI_BIN_DIR/.aspire-install.json注释说明 localhive 与 script 路线共享布局二进制在prefix/bin/下bundle 解压在 bin 的父级nixpackage.nix 在cp -R到$out/lib/aspire-cli/后执行printf %s\n {source:nix} $out/lib/aspire-cli/.aspire-install.json随后用makeWrapper生成$out/bin/aspire包装器dotnet-toolAspire.Cli.csproj 的_PreparePreBuiltCliBinaryForPackTool目标是 dotnet-tool 路线旁车的 source-of-truth 暂存点——它把与 csproj 同目录的dotnet-tool.aspire-install.json复制进RID 专属的 tool nupkgtools/tfm/rid/.aspire-install.json而不复制进跨平台的 pointer nupkg。dotnet-tool nupkg 是唯一在 payload 中内嵌旁车的例外nupkg 是路线独占的只有dotnet tool install会消费它所以内嵌旁车不可能泄漏到其他路线的 prefix 中。pointer nupkg 作为路由 stub 必须不含旁车——它的存在会让 SDK 把 pointer 误判为自包含安装破坏消费端的安装发现verify-cli-tool-nupkg.ps1对此有显式断言。为什么共享归档不内嵌旁车一次真实的事故这个设计不是纸上谈兵而是对真实事故的修复。在 PR 16817 之前per-RID 归档通过一个 MSBuild 目标把{source:brew}osx-和{source:winget}win-烘进了归档根部。问题在于osx-tarball 同样被get-aspire-cli-pr.sh消费*于是偷带的brew旁车落进了 script 路线的 prefix——prefix/dogfood/pr-N/bin/.aspire-install.json。BundleService随后按brew的 flat 布局选择binaryDir作为解压目录生成了prefix/dogfood/pr-N/bin/versions/v/而非预期的prefix/dogfood/pr-N/versions/v/。移除该 MSBuild 目标、改为每条路线在安装时刻自己写旁车后per-RID 归档在构造上就是路线无关的route-agnostic从根本上杜绝了这类泄漏。生产者侧不变式构建 / CI 守护契约由两道机械化检查守护MSBuild 构建期拦截Common.projitems 中的_AssertNoSidecarInArchiveStaging目标AfterTargets_PublishProject会在任何.aspire-install.json被暂存进归档输出路径时使构建失败——构建无法产出一个带旁车的 per-RID 归档。注释中同时说明了 dotnet-tool nupkg 是唯一例外及其暂存位置。验证脚本对签名产物运行在 AzDObuild_sign_native管线中执行verify-cli-archive.ps1 的Test-ArchiveSidecar解压产物归档并递归断言不存在.aspire-install.json同时校验归档文件名可识别 RID 家族verify-cli-tool-nupkg.ps1 断言 dotnet-tool 的 RID 包必须在tools/tfm/rid/.aspire-install.json含有旁车并解析其 JSON 内容同时断言 pointer 包不含旁车。运行时侧不变式Reader-side InvariantsBundleService.ComputeDefaultExtractDir是布局选择的唯一真相源CliPathHelper.GetAspireHomeDirectory是 home 选择的唯一真相源。二者都读取同一个旁车但关注点分离前者决定 bundle 解压形状后者决定状态位置。未知值与只读路线统一回退未知source值以及nix这类已知只读的包路线都回退到默认 Aspire home使无法识别的安装不会试图在 CLI 二进制旁边写入。安装发现的信任信号是旁车本身的存在。对只读安装发现aspire doctor --format json任何可读旁车的候选二进制都会被探测即使其source不在已知路线表中——原始source字符串会作为安装route直接透出这样未来新出现的包管理器路线可以先于本消费方更新而正常显示。无旁车、不可读或格式错误的候选则只列出而不执行其二进制。dotnet-tool 路线的发现范围有限安装发现只遍历默认dotnet tool install -g位置~/.dotnet/tools/.store/aspire.cli。自定义--tool-path的安装目前不会被发现——原因是 dotnet CLI 没有可枚举任意--tool-path安装的全局注册表遍历文件系统又会使aspire doctor的代价不可接受。使用自定义--tool-path的用户可以直接运行tool-path/aspire doctor --self自行确认。小结契约如何贯穿全链路从源码结构看.aspire-install.json的契约贯穿了 Aspire CLI 分发链路的每一层安装脚本在解压后写source可附channel构建管线用 MSBuild 目标和验证脚本双向锁死共享归档无旁车 / tool nupkg 有旁车运行时由 InstallSidecarReader.cs、BundleService.cs、CliPathHelper.cs 三个单点分别负责读取、布局与 home 决策InstallSource.cs 保证 wire 字符串与文档契约严格一致而tests/Aspire.Cli.Tests下的跨路线 theory 测试则把 source × 目录形状的组合矩阵含跨路线错配场景固化为回归防护。理解这套机制的关键在于一个原则路线标签只属于安装时刻不归属于任何共享产物——归档保持路线无关标签由各路线在落地时自己盖写布局与状态位置由此成为source的纯函数。【免费下载链接】aspireAspire is the tool for code-first, extensible, observable dev and deploy.项目地址: https://gitcode.com/GitHub_Trending/as/aspire创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考