news 2026/10/4 1:43:16

SoundSwitch 发布工具链详解:从构建环境到 Release 发布的全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SoundSwitch 发布工具链详解:从构建环境到 Release 发布的全流程指南
  • 桌面应用

【免费下载链接】SoundSwitch

C# application to switch default playing device. Download: https://soundswitch.aaflalo.me/

项目地址:https://gitcode.com/gh_mirrors/so/SoundSwitch
点击查看免费下载

本文聚焦 SoundSwitch 仓库tools/目录下的 PowerShell 7+ 发布工具链,逐一拆解环境准备(Install-BuildTools.ps1)、安装包编译与签名(Build-Installer.ps1 / Sign-Binary.ps1)、完整发布编排(Publish-Release.ps1)、Markdown 文档转 HTML(markdown_to_html.py)与夜间构建上传(upload_nightly_r2.py)五个环节。读完本文,你将掌握 SoundSwitch 从源码/草稿 Release 一路走到正式发布、再到夜间构建分发的完整自动化路径,并理解每一条命令背后的源码级实现细节。

工具链概览与运行前提

tools/目录是 SoundSwitch 发布流程的核心所在,目录入口说明见 tools/README.md,其中包含以下工具:

工具脚本职责
Install-BuildTools.ps1在全新 Windows 11 机器上一键安装全部构建与签名工具
Sign-Binary.ps1用 signtool 对可执行文件做 SHA-256 签名与 RFC 3161 时间戳
Build-Installer.ps1编译并签名 Inno Setup 安装包(不负责源码构建与发布)
Publish-Release.ps1完整发布编排:产物准备 → 文档生成 → 安装包 → 上传草稿 Release → 发布
markdown_to_html.py将 Markdown 转为独立 HTML 文档(替代旧的markdown-htmlnpm 包)
upload_nightly_r2.py上传夜间构建压缩包到 Cloudflare R2 并通知 Discord
vswhere.exe定位 Visual Studio 安装位置(发布流程辅助)

运行前提(硬性要求):所有 PowerShell 脚本都要求PowerShell 7+(Windows 11 自带),与 Windows PowerShell 5.1 不兼容。脚本开头均有#Requires -Version 7.0指令(参见 Install-BuildTools.ps1 第 34 行),在 5.1 下运行会直接报错。Python 脚本则要求 Python 3 及markdown包。

一、环境准备:Install-BuildTools.ps1 一键安装

Install-BuildTools.ps1 面向"全新 Windows 11 机器",建议只运行一次。它基于 winget 安装以下组件,并通过-Scope参数控制安装范围(machine默认、需管理员权限;user免提权):

组件winget 包 ID用途
GitHub CLIGitHub.cliPublish-Release.ps1通过gh与 GitHub Releases 交互
Inno Setup 6JRSoftware.InnoSetup安装包编译器(ISCC.exe)
Certum SimplySign DesktopCertum.SmartSignSimplySignDesktop代码签名用的云证书提供商
Python 3.14Python.Python.3.14运行 markdown-to-HTML 文档生成
.NET SDKMicrosoft.DotNet.SDK.<major>编译与测试应用,版本从工程自动推导

其中.NET SDK的版本号并非写死,而是通过Get-DotNetSdkVersion函数从 SoundSwitch/SoundSwitch.csproj 的<TargetFramework>net10.0-windows10.0.17763.0</TargetFramework>正则提取主版本号(当前为 10),再安装对应的Microsoft.DotNet.SDK.10。这一点在 Install-BuildTools.Tests.ps1 中有专门的Get-DotNetSdkVersion测试组验证(如net10.0-windows→ 10、net9.0→ 9)。

signtool 的四级获取策略

签名工具signtool.exe的获取是脚本中最讲究的部分(Install-SignTool函数),依次尝试:

  1. 已存在于 PATH 中,直接复用;
  2. 在C:\Program Files (x86)\Windows Kits\10\bin下递归查找x64目录中的 signtool.exe,取版本号最高者(Find-SignToolInWindowsKits,排序逻辑见 Install-BuildTools.Tests.ps1 中"返回最高版本"的测试用例);
  3. 从 GitHub 上的 Delphier/SignTool 发布页下载独立轻量版signtool.exe,解压后执行无参冒烟测试(Test-SignToolWorks:退出码 0 或 1 均视为可运行,因为无参 signtool 会打印用法并以 1 退出)确认可用,缓存在%LOCALAPPDATA%\SignTool,下次直接复用;
  4. 兜底:用 winget 安装完整 Windows SDK(Microsoft.WindowsSDK.10.0.26100),再回到第 2 步查找。

无论哪条路径成功,脚本都会把 signtool 目录同时加入当前会话 PATH与用户级 PATH([System.Environment]::SetEnvironmentVariable('Path', ..., 'User')),保证后续Sign-Binary.ps1/Build-Installer.ps1能找到它。安装完成后脚本会提示重启终端以刷新 PATH。

二、代码签名:Sign-Binary.ps1 的现代签名实践

Sign-Binary.ps1 是签名操作的最小封装,被Build-Installer.ps1自动调用,也可独立使用。其核心参数:

参数默认值说明
-Path必填一个或多个待签名文件,支持管道输入与-FullName别名
-CertificateNameOpen Source Developer Antoine Aflalo证书主题名(CN),按名称在证书库中定位证书
-TimestampUrlhttp://timestamp.digicert.comRFC 3161 时间戳服务器
-MaxRetries3单文件最大签名尝试次数
-RetryDelaySec5失败重试间隔(秒)

实际签名的 signtool 命令(第 119-124 行)为:

signtool.exe sign /n $CertificateName /fd sha256 /tr $TimestampUrl /td sha512 /v $resolvedPath

要点解读:

  • 文件摘要用 SHA-256(/fd sha256),时间戳摘要用 SHA-512(/td sha512),完全弃用已过时的 SHA-1;
  • 时间戳走 RFC 3161 协议(/tr),即使证书过期签名依然有效;
  • 每个文件最多重试-MaxRetries次,主要应对时间戳服务器临时不可用等瞬时故障(见脚本第 105-133 行的重试循环);
  • 定位 signtool 的方式与Install-BuildTools.ps1一致:先查 PATH,再回退到 Windows Kits 目录,找不到则直接抛错提示先运行Install-BuildTools.ps1。

独立用法示例:

# 签名单个可执行文件 .\tools\Sign-Binary.ps1 -Path Final\SoundSwitch.exe # 一次签名多个文件 .\tools\Sign-Binary.ps1 -Path Final\SoundSwitch.exe, Final\SoundSwitch.CLI.exe # 使用自定义证书名签名安装包 .\tools\Sign-Binary.ps1 -Path Final\Installer\SoundSwitch_Installer.exe -CertificateName "My Cert"

三、安装包编译:Build-Installer.ps1

Build-Installer.ps1 职责单一:仅编译并签名安装包,不涉及源码构建、文档生成或 GitHub 交互(这些归Publish-Release.ps1)。其参数:

参数默认值说明
-FinalDir仓库根下Final\存放二进制、文档、资源的目录;必须是规范的Final\目录
-SkipSigning关跳过签名(即使 signtool 可用)
-CertificateNameOpenSource Developer, Antoine Aflalo传递给Sign-Binary.ps1
-InstallerReleaseStateRelease传给 Inno Setup 的发布状态标签(Release/Beta/Nightly 等)
-Architectureswin-x64, win-arm64逗号分隔字符串或数组均可,每个架构单独跑一次 ISCC

为什么-FinalDir必须是规范目录

脚本第 87-94 行做了硬校验:-FinalDir必须解析为仓库根下的Final\,否则直接抛错。原因在于 Installer/scripts/app_defines.iss 通过硬编码相对路径..\Final\引用打包载荷(ExeDir定义)。若允许非规范目录,ISCC 实际打包的目录会与脚本签名/清理的目录不一致——校验发生在任何破坏性操作(清理、签名)之前,避免误清误签。

工作流程

  1. 校验载荷:Final\必须存在且非空,否则抛错提示先用Publish-Release.ps1填充;
  2. 签名二进制(未跳过且找到 signtool 时):递归查找Final\下文件名匹配*SoundSwitch*.exe/*SoundSwitch*.dll的文件(排除Installer\子目录),交给Sign-Binary.ps1;
  3. 定位 Inno Setup:与 CI 工作流(test-installer-build.yml)逻辑一致,先查注册表HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1与 WOW6432Node 变体,再回退查 PATH;
  4. 逐架构编译:先清理Final\下旧安装包,再对每个架构映射win-x64 → x64、win-arm64 → arm64,执行ISCC.exe setup.iss /DReleaseState=<状态> /DTargetArch=<架构>,输出从Final\移动到Final\Installer\;
  5. 签名安装包:对Final\Installer\*Installer*.exe再次调用Sign-Binary.ps1。

与 setup.iss 的联动

Installer/setup.iss 顶部强制要求编译时通过/DTargetArch传入x64或arm64,未定义或非法值直接#error。#if TargetArch == "x64"分支还决定:

  • 载荷目录:Final\win-x64\或Final\win-arm64\(app_defines.iss);
  • 安装包文件名后缀:x64 保留无后缀的传统名(OutputBaseFilename为SoundSwitch_v<版本>_<ReleaseState>_Installer.exe),arm64 追加_arm64(SoundSwitch_v<版本>_<ReleaseState>_Installer_arm64.exe);
  • 架构约束:x64 用x64compatible,arm64 用arm64(ArchitecturesAllowed/ArchitecturesInstallIn64BitMode,即只支持 64 位 Windows,x86 不在支持范围);
  • 应用版本号:MyAppVersion通过GetVersionNumbersString从所选架构载荷中的SoundSwitch.exe读取。

setup.iss 还内置了多语言支持(英文、德文、法文、西班牙文、意大利文、葡萄牙文、俄文、波兰文、荷兰文、简体中文、韩文,语言文件见 Installer/Languages)、桌面图标/加入 PATH/删除旧设置三个可选任务、卸载时移除根证书与发布者证书的certutil步骤等。注意:代码签名已不在 setup.iss 中通过SignTool=指令完成,而是统一由Build-Installer.ps1调用Sign-Binary.ps1事后签名(setup.iss 第 52-54 行有注释说明)。

四、完整发布编排:Publish-Release.ps1

Publish-Release.ps1 是发布流程的总指挥,把前面各工具串成一条流水线。参数如下:

参数默认值说明
-Channelreleaserelease(找非预发布草稿)或beta(找预发布草稿);仅下载模式使用
-RepositoryBelphemur/SoundSwitchGitHub 仓库(owner/repo)
-BuildFromSource关改为从源码构建,跳过发布步骤且不要求gh
-ConfigurationRelease构建配置:Release/Debug/Nightly;仅源码构建模式有效
-SkipSigning关跳过签名
-CertificateNameOpen Source Developer Antoine Aflalo传递给Build-Installer.ps1/Sign-Binary.ps1
-InstallerReleaseState自动未显式指定时由-Channel推导:beta →Beta,release →Release
-Architectureswin-x64, win-arm64支持的架构,逗号分隔或数组;夜间构建只传win-x64

两种入口模式

默认模式(下载草稿 Release):要求已安装并登录 GitHub CLI(gh auth login)。Find-DraftRelease用gh release list --repo ... --json tagName,isDraft,isPrerelease,name --limit 30拉取最近 30 个 Release,按通道筛选出最新的草稿(beta 通道选isPrerelease的草稿,release 通道选非预发布草稿),找不到就抛错提示"Has semantic-release run?"——说明草稿由 CI 侧的 semantic-release 预先创建,脚本消费它而非创建它。

-BuildFromSource模式:不依赖gh,直接dotnet publish -c <Configuration> -r <rid> --self-contained true编译SoundSwitch.CLI与SoundSwitch两个项目,产物归入Final\<rid>\;同时执行第 2 步文档生成与资源打包,并跳过第 4-6 步(没有草稿可发布)。

六步流水线详解

Step 1:填充Final\目录。默认模式用gh release download <tag> --pattern 'SoundSwitch-v*.zip'下载 CI 构建产物 zip(要求恰好一个匹配文件,多个会抛错),解压到Final\;源码构建模式则先清理bin/obj/Release/Final再做上述自包含发布。

Step 2:生成 HTML 文档并打包资源(仅源码构建模式)。调用markdown_to_html.py将以下文件转为独立 HTML:

源文件输出
CHANGELOG.mdFinal\Changelog.html
README.mdFinal\Readme.html
Terms.mdFinal\Terms.html
README.de.mdFinal\Readme.de.html

同时把img\soundSwitched.png、SoundSwitch.CLI\README.md、LICENSE.txt、Terms.txt复制进Final\。文件缺失时打印"跳过"而不中断。下载模式中这些内容已由 CI 打进 zip,因此跳过本步。

Step 3:委托Build-Installer.ps1,透传FinalDir、InstallerReleaseState、CertificateName、Architectures(和可选的SkipSigning)。

Step 4:上传安装包到草稿 Release。对Final\Installer\*Installer*.exe逐个执行gh release upload <tag> <file> --repo <repo> --clobber,--clobber用于覆盖同名旧资产。

Step 5:用 CHANGELOG 设置 Release 正文。Get-LatestChangelogEntry从 CHANGELOG.md 提取第一个## [段直到下一个## [段;随后交互式询问是否在正文前追加额外说明(直接回车跳过),写入临时文件后gh release edit --notes-file更新。

Step 6:确认后发布。打印 Release 名称、Tag、通道与安装包数量,等待(y/N)确认后执行gh release edit <tag> --draft=false正式发布;输入N则保持草稿状态,并提示可稍后用同一条命令手动发布。

常用命令速查

# 稳定版完整发布(默认走最新 release 草稿) .\tools\Publish-Release.ps1 # 发布最新 beta 草稿 .\tools\Publish-Release.ps1 -Channel beta # 仅从源码构建安装包,不发布 .\tools\Publish-Release.ps1 -BuildFromSource # 完整发布但不签名 .\tools\Publish-Release.ps1 -SkipSigning # 源码构建,只打 x64 安装包 .\tools\Publish-Release.ps1 -BuildFromSource -Architectures win-x64

脚本质量保障

tools/Install-BuildTools.Tests.ps1 中的 Pester 测试不仅覆盖Install-BuildTools.ps1的辅助函数(通过解析 AST 只加载函数定义、mock 掉 winget/signtool 实现完全隔离),还包含对Publish-Release.ps1/Build-Installer.ps1的语法解析校验(ParseFile断言无错误、含#Requires -Version 7.0),以及"-InstallerReleaseState未显式指定时由-Channel推导"、"Build-Installer.ps1不再使用遗留的Make-Installer.bat、直接调用 ISCC.exe、通过Sign-Binary.ps1签名"等行为断言——这些测试固化了工具链的契约,防止回归。

五、文档转换:markdown_to_html.py

markdown_to_html.py 取代了此前流水线使用的markdown-htmlnpm 包,纯 Python 实现,依赖 PyPI 的markdown包:

pip install markdown

用法:

# 单文件转单 HTML python tools/markdown_to_html.py README.md -o Final/Readme.html # 多文件批量转到一个目录 python tools/markdown_to_html.py CHANGELOG.md Terms.md -d Final

实现要点:

  • 启用extra(表格、围栏代码块、脚注)、codehilite(代码高亮,无 Pygments 时优雅降级)、toc([toc]占位符支持)、sane_lists四个扩展;
  • 输出为独立 HTML5 文档,内嵌完整的 GitHub 风格 CSS(_HTML_TEMPLATE),不需要外部样式表即可直接浏览;
  • 标题由文件名推导:path.stem.replace("-", " ").replace("_", " ").title();
  • -o与-d互斥(argparse 互斥组);-o只允许单个输入文件,多文件配-o直接报错退出;
  • 文件不存在或不可读时以错误信息退出,输出统一打印输入 -> 输出映射行。

六、夜间构建分发:upload_nightly_r2.py

upload_nightly_r2.py 负责把夜间构建(nightly)压缩包上传到Cloudflare R2(S3 兼容对象存储),并可选向 Discord 发送通知。夜间构建在本仓库中的定位可从 website/src/advanced/nightly.md 印证:它们是未经测试、未签名的最新快照,供尝鲜、翻译验证或开发者指定测试,列表展示最近 5 个构建并带 SHA-512 校验和。

命令行参数(全部必填除标注外):

参数必填说明
--file是要上传的构建压缩包
--version是版本号
--bucket是R2 bucket 名
--account-id是Cloudflare 账户 ID
--access-key-id/--secret-access-key是R2 API 凭据
--public-base-url否公共下载 URL 前缀,空则跳过 URL 生成
--prefix否对象前缀,默认nightly
--metadata-file否版本元数据 JSON 输出路径
--discord-webhook否Discord webhook URL,空则跳过通知
--repository否用于提交链接格式化
--commit-count否通知里展示的近期提交数,默认 10
--commit否当前构建对应的 commit

核心流程与细节:

  1. 客户端:用 boto3 构造 S3 客户端,endpoint 为https://<account-id>.r2.cloudflarestorage.com,region 固定auto;
  2. 读取既有元数据:从nightly/version.json读取历史 artifact 列表(兼容新旧两种元数据格式),用于计算增量 changelog 与清理旧文件;
  3. SHA-512 校验和:分块计算(1 MiB 块),存入 artifact 记录,供 SoundSwitch 更新器下载前校验(对应 nightly 文档中"updater 依据 SHA-512 校验"的说明);
  4. Changelog 生成:用git log --no-merges --oneline取上一个 commit 到当前 commit 的提交(最多 20 条),按 Conventional Commits 格式正则^([\da-f]{7,})\s(\w+)\((.+)\)(.+)$解析并渲染为带链接的列表;
  5. 上传与保留策略:上传nightly/<文件名>,同时只保留最近 9 个 artifact,多余的从 bucket 删除(retained_artifacts[:9]+ 计算 delete_keys);随后更新nightly/version.json(含latest、published、url、artifacts数组);
  6. 通知与 CI 输出:写本地元数据文件(若指定);Discord webhook 发送带 embed 的消息(标题New Build: <version>、最近 N 条提交、时间戳);若环境变量GITHUB_OUTPUT存在,追加version与download-url两个输出供 CI 下游步骤使用。

七、工具链在发布流水线中的整体协作

把上述脚本串起来,SoundSwitch 的发布流程可以概括为三条路径:

路径 A(默认,草稿 Release 驱动): CI(semantic-release 创建草稿 + 构建产物 zip) → Publish-Release.ps1 下载 zip 到 Final\ → Build-Installer.ps1(内部调用 Sign-Binary.ps1 签二进制与安装包) → gh release upload --clobber 上传安装包 → 提取 CHANGELOG 最新段落作为正文(可追加用户说明) → 确认后 gh release edit --draft=false 发布 路径 B(本地从源码构建): Publish-Release.ps1 -BuildFromSource → dotnet publish 自包含发布 CLI + 主程序(按架构) → markdown_to_html.py 生成 Changelog/Readme/Terms 的 HTML → 复制 README/LICENSE/Terms/图片资源 → Build-Installer.ps1 编译并签名安装包 (不涉及 GitHub,无发布步骤) 路径 C(夜间构建分发): 构建产物 → upload_nightly_r2.py → Cloudflare R2(保留最近 9 个 + version.json 元数据 + SHA-512) → Discord 通知 + GITHUB_OUTPUT 输出

值得一提的是,仓库根目录还保留了 Make.bat 与 Installer/Make-Installer.bat 作为遗留的批处理构建入口(前者同样执行自包含发布与markdown-html文档生成,后者按x64,arm64逐架构调用 ISCC),而tools/下的 PowerShell 脚本是其现代化替代——测试用例明确断言Build-Installer.ps1"does not use Make-Installer.bat (legacy)"。同时注意,这些脚本面向 Windows 开发机,与 docs/architecture.md 描述的 .NET/WinForms 应用结构互为表里:发布链路服务于主应用(SoundSwitch/)、CLI(SoundSwitch.CLI/)以及多语言安装包这一整套交付物。

参考文件索引

  • 工具说明总览:tools/README.md
  • 环境安装:tools/Install-BuildTools.ps1、测试 tools/Install-BuildTools.Tests.ps1
  • 代码签名:tools/Sign-Binary.ps1
  • 安装包编译:tools/Build-Installer.ps1、脚本 Installer/setup.iss、定义 Installer/scripts/app_defines.iss
  • 发布编排:tools/Publish-Release.ps1
  • 文档转换:tools/markdown_to_html.py
  • 夜间构建:tools/upload_nightly_r2.py、说明 website/src/advanced/nightly.md
  • 遗留批处理:Make.bat、Installer/Make-Installer.bat
  • 桌面应用

【免费下载链接】SoundSwitch

C# application to switch default playing device. Download: https://soundswitch.aaflalo.me/

项目地址:https://gitcode.com/gh_mirrors/so/SoundSwitch
点击查看免费下载

相关推荐

上一篇:【亲测免费】 React Native BLE Manager 项目常见问题解决方案
下一篇:CANN/asc-devkit SIMD频率统计API

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 1:42:42

PDFsam Basic 批量处理实战:拆分合并旋转提取,敏感文件零上传

PDFsam Basic 批量处理实战&#xff1a;拆分合并旋转提取&#xff0c;敏感文件零上传 在线 PDF 工具方便&#xff0c;但合同、成绩单、身份证扫描件上传到陌生服务器&#xff0c;安全性无从考证。PDFsam Basic&#xff08;开源 AGPL-3&#xff09;把拆分合并旋转提取四大刚需全…

作者头像 李华
网站建设 2026/10/4 1:41:35

Python中常用的列表函数

Python中常用的列表函数函数功能说明len(list)获取列表的长度max(list)/min(list)获取列表中的最大值、最小值sorted(list)对列表排序并返回新的列表list.sort(reverseFalse)对列表排序&#xff08;True为降序&#xff0c;默认值False为升序&#xff09;list.reverse()逆序现有…

作者头像 李华