Masterain

Masterain

使用 Azure Pipelines 為 C# .NET MSIX 應用構建 CI/CD 管道

前言#

去年年底,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 團隊名稱
    • 對於需要免費額度的開源項目而言,需要添加這個表格或發送郵件給[email protected]來申請免費額度
  • 你需要等待 1-2 天等待微軟官方為你的組織添加免費額度以運行 CI/CD 管道。在此之前,你仍然可以繼續構建你的 CI/CD 管道,只是它們會因為額度不足而無法實際運行

創建項目#

  • 進入 DevOps 面板,進入組織頁面,點擊右側的 New Project 來創建一個新的項目

  • 創建成功後選擇 GitHub 作為你的代碼庫

    GitHub as repo

  • 在選擇代碼庫時,在篩選中選擇 All repositories 後找到你需要創建 CI 管道的項目並確認
    Select GitHub Repository

  • 之後頁面會跳轉到 GitHub 授權頁面,在這裡同意 Azure DevOps 編輯該庫

  • 完成授權後,你會被跳轉到一個 YAML 文件編輯器上,這個就是 Azure Pipelines 的配置文件

構建管道#

[tip type="info" title="兼容性問題"]
微軟官方文檔中,他們推薦了由微軟官方維護的 Azure Pipelines 插件,但該插件在寫本文時對 VS 2022 存在兼容性問題。所以在構建 Snap Hutao 項目時,我通過 PowerShell 以命令行的方式打包了應用程序。因此,你也可以通過本文了解 MSIX 打包的細節流程。
[/tip]

準備工作#

在一切開始之前,我們先為管道環境設置好一些環境變量

  • 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 的兼容性問題,我們不得不使用 locationMSBuild.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 證書文件
Upload secure files to libary

回到 Pipelins YAML 文件編輯器,我們需要將 pfx 證書的密碼作為變量儲存在這個管道任務中。點擊右上角的 Variables,點擊添加。在添加變量的窗口中,為變量設置一個名稱,並在Value中填寫密碼,最後勾選 Keep this value secret
pfx password variable

現在我們就可以使用微軟官方的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 的全部任務記錄。
Azure Pipelines history

在 GitHub Release 中你可以看到已發布的 CI/CD 版本
GitHub release

在對應的 Commit 中,你也可以看到對應 CI/CD 任務的信息
Commit information

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。