前言
去年年底,Snap Genshin 项目被归档并被 Snap Hutao 替代。在新一代的应用中,项目使用了更新式的技术,使用winui3 替代了原始的 WPF,并最终打包为 msix 安装包,使其上架到了微软商店。由于商店的审核机制,应用不能再像过去自分发模式一样随时推送更新,因此构建为开发人员测试的 CI/CD 通道版本变得更加重要了。为此,我使用 Azure DevOps 中的 Pipelines 通过其和 GitHub 的集成创建了 CI/CD 管道。
创建 DevOps 帐号
- 来到微软 Azure Pipelines 主页,点击 Start free with GitHub 来通过登录你的微软帐号来进入 Azure DevOps 面板并绑定 GitHub 帐号。
如果你是为团队或公司创建这个环境,建议使用团队微软帐号并在绑定 GitHub 过程中授权 Azure DevOps 使用团队库数据
- 同时,你需要为你的团队创建一个 DevOps 团队名称
- 对于需要免费额度的开源项目而言,需要添加这个表格或发送邮件给
azpipelines-freetier@microsoft.com
来申请免费额度
- 你需要等待1-2天等待微软官方为你的组织添加免费额度以运行 CI/CD 管道。在此之前,你仍然可以继续构建你的 CI/CD 管道,只是它们会因为额度不足而无法实际运行
创建项目
- 进入 DevOps 面板,进入组织页面,点击右侧的
New Project
来创建一个新的项目 创建成功后选择
GitHub
作为你的代码库- 在选择代码库时,在筛选中选择
All repositories
后找到你需要创建 CI 管道的项目并确认
- 之后页面会跳转到 GitHub 授权页面,在这里同意 Azure DevOps 编辑该库
- 完成授权后,你会被跳转到一个
YAML
文件编辑器上,这个就是 Azure Pipelines 的配置文件
构建管道
兼容性问题
在微软官方文档中,他们推荐了由微软官方维护的 Azure Pipelines 插件,但该插件在写本文时对 VS 2022 存在兼容性问题。所以在构建 Snap Hutao 项目时,我通过 PowerShell 以命令行的方式打包了应用程序。因此,你也可以通过本文了解 MSIX 打包的细节流程。准备工作
在一切开始之前,我们先为管道环境设置好一些环境变量
build_date
通过调用环境中的 PowerShell 返回了一个形如2023.2.28
的时间格式,我们将这样的日期作为 CI/CD 测试通道中软件的版本号
variables:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
solution: '$(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao.sln'
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
buildPlatform: 'x64'
buildConfiguration: 'Release'
build_date: $[ format('{0:yyyy}.{0:M}.{0:d}', pipeline.startTime) ]
我们将 CI/CD 环境设置为 Windows Server 2022,该环境下已包含 Visual Studio 2022 企业版
pool:
vmImage: 'windows-2022'
编译二进制文件
- 接下来我们为 .NET Framework 程序创建打包环境
- task: UseDotNet@2
displayName: Install dotNet
inputs:
packageType: 'sdk'
version: '7.x'
includePreviewVersions: true
- task: NuGetToolInstaller@1
name: 'NuGetToolInstaller'
displayName: 'NuGet Installer'
- task: NuGetCommand@2
displayName: NuGet restore
inputs:
command: 'restore'
restoreSolution: '$(solution)'
feedsToUse: 'select'
随后,我们就可以使用 Visual Studio 2022 中包含的 MSBuild
来编译代码了
- 在这一步,大多数情况下我们都只需要
msbuildLocationMethod
设置为version
并将msbuildVersion
设置为lastest
或其它指定版本。但是,由于对 VS2022 的兼容性问题,我们不得不使用location
和MSBuild.exe
的路径作为打包参数
- task: MsixPackaging@1
displayName: Build binary package
inputs:
outputPath: '$(Build.ArtifactStagingDirectory)/'
solution: '$(solution)'
clean: false
generateBundle: false
buildConfiguration: 'Release'
buildPlatform: 'x64'
updateAppVersion: false
appPackageDistributionMode: 'SideloadOnly'
msbuildLocationMethod: 'location'
msbuildLocation: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\MSBuild.exe'
MSIX 打包
在打包之前,我们要准备好打包后的软件包版本号。在已编译的二进制程序中,软件版本号是跟随代码中的设置的,但我们是不可能会为了一个 CI/CD 的临时版本而修改它的,所以我们需要让 CI/CD 管道为我们准备这个临时的版本号。在 CI/CD 管道最初运行时,我们已经通过环境变量获得了一个格式形为2023.2.28
这样的日期,但缺少第四位的 revision number
。非常巧合的是, Azure Pipelines 会为每一个 CI/CD 任务创建一个版本号,其格式形如20230228.1
,其实小数点后的数字代表该日期下的第几次任务,这个数字就非常适合用于 revision number
,因为它可以保证版本号一定是有序递增的。
在 Azure DevOps Market 中我们可以添加一款名为 GetRevision
的插件。使用它,我们就可以添加下面这段脚本将 revision number 设置到 rev_number
变量上。
- task: GetRevision@1
displayName: get Pipelines revision number
inputs:
VariableName: 'rev_number'
除非额外指定参数,编译后的程序会被储存在项目目录下的\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\
目录中
Windows 系统会通过读取 MSIX 应用的 Identity Name
来作为应用的唯一标识,因此我们需要更改一些信息以让系统和我们自己来区分 CI/CD 版本应用和正式版应用。在这里,我们使用 MagicChunks
来修改管理这些属性的AppxManifest.xml
文件
transformations
中包含了我们覆盖的属性数值Package/Identity/@Name
就是系统用于分辨软件包唯一标识的名称Package/Identity/@Publisher
是关键的开发者信息,它的值必须和你MSIX应用包签名完全一致,否则会产生签名错误- 如果你的代码签名证书是购买来的,有时候它会包含多重信息,比如公司名称、组织名称以及邮箱。这种情况下,一些标点符号就会和
xml
格式发生冲突。因此,我们需要对这些信息中有冲突的符号进行转义,并且需要包含这些全部信息,而不仅仅是CN
。
- 如果你的代码签名证书是购买来的,有时候它会包含多重信息,比如公司名称、组织名称以及邮箱。这种情况下,一些标点符号就会和
"Package/Identity/@Version": "$(build_date).$(rev_number)"
这一行将我们之前准备好的应用包版本写入属性
- task: MagicChunks@2
inputs:
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\AppxManifest.xml'
fileType: 'Xml'
targetPathType: 'source'
transformationType: 'json'
transformations: |
{
"Package/Identity/@Name": "7f0db578-026f-4e0b-a75b-d5d06bb0a74c",
"Package/Identity/@Publisher": "CN=DGP Studio CI",
"Package/Identity/@Version": "$(build_date).$(rev_number)",
"Package/Properties/DisplayName": "胡桃 Alpha",
"Package/Properties/PublisherDisplayName":"DGP Studio CI",
"Package/Applications/Application/uap:VisualElements/@DisplayName": "胡桃 Alpha"
}
接下来,我们将应用程序中使用的静态资源也加入二进制程序目录下。这些资源在代码中是一种外部调用,所以在二进制编译过程中,它们是不会被自动添加到编译后的目录中的。
- task: CmdLine@2
displayName: Create resources folder
inputs:
script: |
mkdir Assets
mkdir Resource
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64'
- task: CopyFiles@2
displayName: Copy Assets Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Assets'
- task: CopyFiles@2
displayName: Copy Resource Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Resource'
一切就绪,我们现在可以通过makeappx
将二进制文件打包为 MSIX 应用包了
- 其中,
$(Build.ArtifactStagingDirectory)
是 Azure Pipelines 默认的导出资源目录
- task: CmdLine@2
displayName: Build MSIX
inputs:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
签名 MSIX 应用包
MSIX 应用包要求所有应用都需要代码签名,否则应用无法安装。所以接下来,我们将要为已打包的 MSIX 包签名。
点击 Pipelines 菜单中的 Library
,进入 Secure files
,点击按钮上传设有密码的 pfx 证书文件
回到 Pipelins YAML 文件编辑器,我们需要将 pfx 证书的密码作为变量储存在这个管道任务中。点击右上角的 Variables
,点击添加。在添加变量的窗口中,为变量设置一个名称,并在Value
中填写密码,最后勾选 Keep this value secret
现在我们就可以使用微软官方的MsixSigning
插件来签名 Msix 应用包
- 其中
certificate
为你在Secure Files
中上传的证书文件名 passwordVariable
为你储存 pfx 证书密码的变量名称
- task: MsixSigning@1
name: signMsix
displayName: Sign MSIX package
inputs:
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
certificate: 'DGP_Studio_CI.pfx'
passwordVariable: 'pw'
如果你希望使用 PowerShell 或 CMD 来签名可以参考以下的命令
SignTool sign /fd SHA256 /a /f C:\Users\i\Documents\GitHub\Snap.Hutao.Output\Snap.Hutao_TemporaryKey.pfx /p defaultpassword C:\Users\i\Documents\GitHub\Snap.Hutao.Output\Snap.Hutao.signed.msix
发布 CI/CD 应用
在 Snap Hutao 项目中,我决定将侧载包和 CI/CD 侧载证书一起以 pre-release
的方式在 GitHub 主项目库中发布。其中,
Download Root CA
任务将储存在Secure Files
中的侧载证书下载到 CI/CD 环境中,并将该文件以cerFile
名称储存在环境变量中。在 GitHub Release 发布流程中,我们可以通过$(cerFile.secureFilePath)
来引用该文件gitHubConnection
是集成的 GitHub 帐号
- task: DownloadSecureFile@1
name: cerFile
displayName: Download Root CA
inputs:
secureFile: 'Snap.Hutao.CI.cer'
- task: GitHubRelease@1
inputs:
gitHubConnection: 'github.com_Masterain'
repositoryName: 'DGP-Studio/Snap.Hutao'
action: 'create'
target: '$(Build.SourceVersion)'
tagSource: 'userSpecifiedTag'
tag: '$(build_date).$(rev_number)'
title: '$(build_date).$(rev_number)'
releaseNotesSource: 'inline'
releaseNotesInline: |
## 普通用户请勿下载
该版本是由 CI 程序自动打包生成的 `Alpha` 测试版本,**仅供开发者测试使用**
普通用户请[点击这里](https://github.com/DGP-Studio/Snap.Hutao/releases/latest/)下载最新的稳定版本
assets: |
$(Build.ArtifactStagingDirectory)/*
$(cerFile.secureFilePath)
isPreRelease: true
changeLogCompareToRelease: 'lastFullRelease'
changeLogType: 'commitBased'
成果
点击 YAML 编辑器右上角的保存,你的 CI/CD 配置文件就会被添加到 GitHub 代码库中,并立刻执行一次。在 Azure DevOps 中你可以看到 Pipelines 的全部任务记录。
在 GitHub Release 中你可以看到已发布的 CI/CD 版本
在对应的 Commit 中,你也可以看到对应 CI/CD 任务的信息
转载请标注来源