mRemoteNG 双轨部署方案全解析:Framework-Dependent 与 Self-Contained 构建实战
【免费下载链接】mRemoteNGmRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager.项目地址: https://gitcode.com/gh_mirrors/mr/mRemoteNG
mRemoteNG 是开源的多协议远程连接管理器,其 .NET 10 时代引入的两种部署形态——框架依赖版(Framework-Dependent,简称 FD)与自包含版(Self-Contained,简称 SC)——直接决定了产物大小、运行时依赖与分发策略。本文以仓库根目录下的 DEPLOYMENT_OPTIONS.md 为核心骨架,结合 mRemoteNG.csproj、ProgramRoot.cs 与 CI 工作流等源码证据,完整讲解两种部署类型的差异、本地构建命令、编译常量机制、自动发布流程以及验证与迁移方法。读完本文,你将能独立构建、验证并发布 x64 / ARM64 双架构、FD / SC 双形态的 mRemoteNG 安装包。
一、两种部署类型的定位与取舍
mRemoteNG 的发布产物通过文件后缀区分:-FD.zip表示框架依赖版,-SC.zip表示自包含版。两者的本质区别在于是否把 .NET 运行时打包进产物。
| 维度 | Framework-Dependent(FD) | Self-Contained(SC) |
|---|---|---|
| 文件后缀 | -FD.zip | -SC.zip |
| 体积 | 约 15~25 MB | 约 80~150 MB |
| 前置条件 | 用户需自行安装 .NET 10 Desktop Runtime | 无,运行时已内置 |
| 启动行为 | 检查 .NET 运行时与 Visual C++ Redistributable,缺失时提示下载 | 不做运行时检查(运行时已打包) |
| 典型场景 | 面向可接受安装前置依赖的标准用户 | U 盘便携、受限环境、零安装配置需求 |
FD 版体积小、更新快,且可与系统上其他 .NET 应用共享运行时,适合作为标准发行版;SC 版虽然体积接近百 MB,但"解压即用",无需任何前置环境准备,是便携部署的唯一选择。从 mRemoteNG.csproj 可以看到,工程显式声明了Configurations>Debug;Release;Release Self-Contained;Deploy to github四种配置,其中Release Self-Contained就是为自包含构建单独设计的编译配置,印证了双轨发布是官方一等公民能力。
体积差异的根源:SC 版额外携带了整个 .NET 10 运行时,约 80 MB 的固定开销,这是文档中"Self-contained includes entire .NET 10 runtime (~80 MB overhead)"的直接来源。
二、本地构建:Framework-Dependent 版
FD 构建走传统的 MSBuild 路径,使用msbuild直接编译解决方案:
# x64 msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64 # ARM64 msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64此命令会以Release配置编译 mRemoteNG.sln(仓库根目录)下的全部工程。对应到 mRemoteNG.csproj,Release|x64与Release|arm64两个属性组不定义任何特殊编译常量、不设置RuntimeIdentifier,输出分别落入bin\x64\Release\与bin\arm64\Release\,产物即纯框架依赖形态。
三、本地构建:Self-Contained 版
SC 构建需要dotnet publish指定运行时标识(RID)并开启自包含开关:
# x64 dotnet publish mRemoteNG\mRemoteNG.csproj ` --configuration Release ` --runtime win-x64 ` --self-contained true ` -p:Platform=x64 ` -p:PublishSingleFile=false ` -p:PublishReadyToRun=true ` -p:DefineConstants="SELF_CONTAINED" # ARM64 dotnet publish mRemoteNG\mRemoteNG.csproj ` --configuration Release ` --runtime win-arm64 ` --self-contained true ` -p:Platform=ARM64 ` -p:PublishSingleFile=false ` -p:PublishReadyToRun=true ` -p:DefineConstants="SELF_CONTAINED"参数逐一说明:
--runtime win-x64 / win-arm64:目标运行时标识,与 mRemoteNG.csproj 中声明的RuntimeIdentifiers>win-x64;win-arm64对应;--self-contained true:将 .NET 10 运行时整体打包;-p:PublishSingleFile=false:保持文件分离。这一选择与 mRemoteNG 的插件机制强相关——工程中通过CopyPackageAssembliesToSubFolderTarget(见 mRemoteNG.csproj)把 NuGet 依赖程序集统一落到Assemblies\子目录,由 ProgramRoot.cs 中的OnAssemblyResolve事件按需加载,单文件发布会破坏这种"程序集延迟解析"布局;-p:PublishReadyToRun=true:启用 ReadyToRun(R2R)预编译,将 IL 预编译为原生代码,缩短冷启动时间,代价是体积略有增加;-p:DefineConstants="SELF_CONTAINED":定义编译常量(详见第五节)。
另一种等效方式:工程内置的 Release Self-Contained 配置
除了命令行传参,仓库还内置了完整的自包含构建配置。在 mRemoteNG.csproj 中,Release Self-Contained|x64/arm64属性组已经写死了SelfContained=true、RuntimeIdentifier=win-x64/win-arm64、PublishReadyToRun=true与DefineConstants=PORTABLE,因此直接执行:
msbuild mRemoteNG\mRemoteNG.csproj -p:Configuration="Release Self-Contained" -p:Platform=x64即可触发构建。更关键的是,工程末尾的PublishAfterBuildTarget(mRemoteNG.csproj)会在该配置构建完成后自动级联执行Publish目标,并把临时编译目录清理掉,最终发布产物落在bin\x64\Publish Self-Contained\(x64)与bin\arm64\Publish Self-Contained\(arm64)。CI 工作流正是复用了这套配置(见第六节)。
四、启动时的运行时检查:源码级实现
FD 版与 SC 版在启动行为上的差异,体现在 ProgramRoot.cs 的MainAsync入口中。实际实现比文档中描述的#if直接裁剪要更精细——代码通过ShouldSkipNativeRuntimeChecks(args)统一决策,自包含构建(IsPortableBuild为 true)直接跳过整个检查块:
private static Task MainAsync(string[] args) { AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; if (!ShouldSkipNativeRuntimeChecks(args)) { // Runtime checks only needed for framework-dependent deployments // Self-contained builds include the runtime, so no check is needed // Note: .NET runtime check is not needed here — the .NET host (apphost) // natively displays a missing-runtime dialog with a download link. var checkFail = false; // Checking Visual C++ Redistributable version if (VCppRuntimeCheck.GetInstalledVcRedistVersions() == null || VCppRuntimeCheck.GetInstalledVcRedistVersions().Count == 0) { // 弹出 Download/Cancel 对话框,引导用户下载 vc_redist.x64.exe // ... checkFail = true; } if (checkFail) { Environment.Exit(0); } } // ... }这里有两点值得注意的源码细节:
- .NET 运行时缺失的提示并非由应用代码完成:源码注释明确指出,
apphost(.NET 生成的应用程序宿主)在运行时缺失时会原生弹出带下载链接的对话框,因此MainAsync只需关心Visual C++ Redistributable是否安装; - VC++ 运行时的检测逻辑:位于 VCppRuntimeCheck.cs,它遍历注册表
SOFTWARE\Microsoft\VisualStudio与SOFTWARE\WOW6432Node\Microsoft\VisualStudio下14.0 ~ 17.3各版本的VC\Runtimes\x64键,检查Installed值是否为 1;一旦检测到缺失,应用会弹出含下载链接的对话框,并在用户确认后通过Process.Start打开https://aka.ms/vs/17/release/vc_redist.x64.exe下载页,随后Environment.Exit(0)退出。
检查的绕过机制
ProgramRoot.cs 的ShouldSkipNativeRuntimeChecks提供了三套跳过途径,对运维和排障很有价值:
- 便携构建:
IsPortableBuild(即#if PORTABLE常量,见 ProgramRoot.cs)为 true 时直接跳过; - 命令行开关:启动参数携带
--skip-runtime-checks(不区分大小写)时跳过; - 环境变量:设置
MREMOTENG_SKIP_RUNTIME_CHECKS=1(或true/TRUE)时跳过。
这些分支均有单元测试覆盖:mRemoteNGTests/App/ProgramRootTests.cs中的ShouldSkipNativeRuntimeChecks_WhenCliSwitchIsProvided_ReturnsTrue、ShouldSkipNativeRuntimeChecks_WhenEnvironmentRequestsBypass_ReturnsTrue以及验证无任何绕过配置时行为等于ProgramRoot.IsPortableBuild的测试,完整对应上述三条路径。
关于编译常量的一点勘误
DEPLOYMENT_OPTIONS.md 中提到以SELF_CONTAINED常量配合#if !SELF_CONTAINED裁剪运行时检查代码;从当前仓库的实际代码看,自包含裁剪实际由PORTABLE常量承担(mRemoteNG.csproj 中Release Self-Contained配置定义的是DefineConstants=PORTABLE,ProgramRoot.IsPortableBuild同样检测PORTABLE)。可以推断SELF_CONTAINED是文档撰写时构想的命名,实际落地沿用了历史遗留的PORTABLE符号,两者功能等价——编译进自包含产物的二进制中不再包含 VC++ 运行时检查逻辑。
五、PORTABLE 符号对相关模块的连锁影响
PORTABLE不只是控制启动检查,它还在全仓库多处条件编译,直接影响自包含版的运行行为:
- 更新机制(AppUpdater.cs):非便携版(
#if !PORTABLE)下载更新后保存到系统临时目录并静默安装 MSI;便携版则弹出SaveFileDialog让用户选择保存位置,由用户手动替换文件; - 更新源(UpdateChannelInfo.cs):定义了
update-portable.txt、preview-update-portable.txt、nightly-update-portable.txt等独立的便携版更新通道文件; - 配置读写:
DockPanelLayoutLoader.cs、ExternalAppsLoader.cs、ChooseProvider.cs中均有#if !PORTABLE/#if PORTABLE分支,决定界面布局、外部工具清单与数据源选择器在便携形态下的行为; - UI 呈现:
FrmAbout.cs中[Conditional("PORTABLE")]方法会在"关于"窗口标注便携版身份。
可见,从"免安装"这一需求出发,PORTABLE贯穿了更新、配置与 UI 三条链路,SC 版不是简单的"FD 版加运行时",而是一套经过条件编译裁切的完整便携形态。
六、GitHub Actions:一次构建四种产物的自动化流水线
仓库 .github/workflows/Build_mR-NB.yml 是文档中提到的多部署工作流(工作流name为Build_and_Release_mR-NB_MultiDeploy)。它通过矩阵策略一次产出四种变体:
| 变体 | 产物命名(示例) | runner |
|---|---|---|
| x64 Framework-Dependent | mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip | windows-2025-vs2026 |
| x64 Self-Contained | mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip | windows-2025-vs2026 |
| ARM64 Framework-Dependent | mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip | windows-11-vs2026-arm |
| ARM64 Self-Contained | mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip | windows-11-vs2026-arm |
触发条件
工作流仅在两种情况下运行(见 Build_mR-NB.yml 的if表达式):
- 推送触发:向
v1.78.2-dev分支推送,且最新提交信息包含NB release; - 手动触发:通过
workflow_dispatch手动运行(带release_flag输入,默认true)。
流水线关键步骤
- 检出代码(
actions/checkout@v7); - 自动发现解决方案:递归查找
.sln或.slnx文件,兼容新老格式; - 安装 .NET 10 SDK(
actions/setup-dotnet@v6)并配置 MSBuild(microsoft/setup-msbuild@v3,按矩阵选择 msbuild 架构); - 解析 MSBuild 环境:定位
msbuild.exe、SDK 基础路径,写入MSBuildSDKsPath、DOTNET_ROOT等环境变量,保证 MSBuild 能找到正确的 .NET SDK; - T4 模板转换:安装
dotnet-t4工具,将 AssemblyInfo.tt 按矩阵平台转换生成AssemblyInfo.cs(版本号即来自此文件); - NuGet 恢复:FD 用
Release配置恢复;SC 用Release Self-Contained配置并追加-p:SelfContained=true -p:RuntimeIdentifier=win-{arch} -p:PublishReadyToRun=true恢复,且缓存键按架构与部署类型区分; - 构建:FD 走
msbuild /p:Configuration=Release;SC 走msbuild /p:Configuration="Release Self-Contained"(同时把PublishDir指到bin\{Platform}\{arch}\Release); - 生成发布信息:从
AssemblyInfo.cs正则提取AssemblyVersion得到版本与构建号,拼出 zip 名与 tag(格式yyyyMMdd-vX.X.X-NB-(build)),并抓取最近一次提交信息; - 提取 CHANGELOG:自动截取 CHANGELOG.md 最新版本段作为发布说明正文;
- 压缩与上传:
Compress-Archive打包整个输出目录为 zip,作为 artifact 上传(保留 1 天)。
合并发布
NB-Build-and-Release完成后,Create-Combined-Release作业汇总四个 artifact,调用softprops/action-gh-release创建单一 GitHub Release,tag 与名称统一(如mRemoteNG vX.X.X NB (build)),发布说明中明确标注:
- Framework-Dependent (FD):需安装 .NET 10 Runtime,体积约 15~25 MB,文件为
*-FD.zip; - Self-Contained (SC):便携免安装,体积约 80~150 MB,文件为
*-SC.zip;
并以prerelease: true发布,正文附带 CHANGELOG 与最近提交信息。整个流水线在仓库侧即可"一次触发、四包齐发、合并发布",无需手工处理各平台产物。
七、如何选择:用户视角与分发视角
面向终端用户
选 Framework-Dependent(FD),如果:
- 不介意一次性安装 .NET 10 Desktop Runtime;
- 希望下载体积小(15~25 MB);
- 机器上已运行多个 .NET 应用,愿意共享同一运行时。
选 Self-Contained(SC),如果:
- 希望零安装、零配置,解压即用;
- 需要 U 盘等便携介质携带(受限环境、无管理员权限的机器);
- 不想处理任何前置依赖。
面向分发者
- 将FD 作为默认/推荐选项:体积小、更新发布快、与系统共享运行时,适合大多数常规用户;
- 将SC 作为便携替代:覆盖特殊场景(隔离网络、临时工作站、企业脱管设备),两种形态互补而不是互斥。当前 CI 工作流的"四个包合并到一个 Release"设计正是这一策略的落地。
八、发布前验证清单
文档给出了两组手工验收步骤,分别验证 FD 与 SC 的运行时处理逻辑:
Framework-Dependent 版:
- 卸载 .NET 10 Runtime(若已安装);
- 运行
mRemoteNG.exe; - 应弹出 .NET 10 Runtime 下载提示(由 apphost 原生触发);
- 安装运行时后再次启动,确认应用正常进入主界面。
Self-Contained 版:
- 卸载 .NET 10 Runtime(若已安装);
- 运行
mRemoteNG.exe; - 应直接启动、无任何运行时检查提示;
- 验证完整功能(连接、配置读写、插件加载等)。
结合第五节可知,SC 版还应顺带验证便携更新流程(保存对话框)与便携配置路径是否按预期工作,因为PORTABLE符号同时裁切了这两部分逻辑。
九、从旧工作流迁移
仓库同时保留了旧版单工作流。若此前一直使用Build_and_Release_mR-NB.yml发布单一版本,迁移到多部署方案只需三步:
- 重命名或删除旧工作流
Build_and_Release_mR-NB.yml(仓库当前实际路径为 .github/workflows/Build_mR-NB.yml,其name已是Build_and_Release_mR-NB_MultiDeploy,可直接复用); - 将新工作流重命名为
Build_and_Release_mR-NB.yml(若需要保持既有触发配置的命名习惯); - 提交并推送,提交信息中包含
NB release以触发四变体构建。
迁移后,Release 页将从"单个 zip"变为"x64/ARM64 × FD/SC 四个 zip 加清晰说明"的结构,用户可按需取用。
十、总结
mRemoteNG 的双轨部署是一套"工程配置 + 条件编译 + CI 矩阵"三层配合的完整方案:工程层通过Release Self-Contained配置固化自包含参数(mRemoteNG.csproj),代码层通过PORTABLE常量统一裁切运行时检查、更新与配置行为(ProgramRoot.cs、AppUpdater.cs),流水线层通过矩阵策略一次产出四种产物并合并发布(Build_mR-NB.yml)。无论是本地手动构建 FD/SC 包,还是维护自动化发布,理解这三层之间的联动关系都是关键。按本文第七、八节的策略与清单执行,即可稳定交付满足不同用户群体需求的 mRemoteNG 发行版。
【免费下载链接】mRemoteNGmRemoteNG is the next generation of mRemote, open source, tabbed, multi-protocol, remote connections manager.项目地址: https://gitcode.com/gh_mirrors/mr/mRemoteNG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考