Masterain

Masterain

Azure Pipelines を使用して C# .NET MSIX アプリの CI/CD パイプラインを構築する

前言#

去年年底、Snap Genshin プロジェクトがアーカイブされ、Snap Hutao に置き換えられました。新世代のアプリケーションでは、プロジェクトは更新された技術を使用し、winui3 が元の WPF に置き換えられ、最終的に msix インストーラーにパッケージ化され、Microsoft ストアに上架されました。ストアの審査メカニズムのため、アプリは以前の自己配布モードのようにいつでも更新をプッシュすることができなくなったため、開発者がテストするための CI/CD チャンネルバージョンを構築することがより重要になりました。そのため、私は Azure DevOps の Pipelines を使用して、GitHub との統合を通じて CI/CD パイプラインを作成しました。

DevOps アカウントの作成#

  • Microsoft Azure Pipelines ホームページ にアクセスし、GitHub で無料で始めるをクリックして、Microsoft アカウントでログインし、Azure DevOps パネルにアクセスして GitHub アカウントをバインドします。

  • チームや会社のためにこの環境を作成する場合は、チームの Microsoft アカウントを使用し、GitHub バインドプロセス中に Azure DevOps にチームリポジトリデータの使用を許可することをお勧めします。

    • 同時に、チームの DevOps チーム名を作成する必要があります。
    • 無料枠が必要なオープンソースプロジェクトの場合は、このフォームを追加するか、[email protected] にメールを送信して無料枠を申請する必要があります。
  • Microsoft があなたの組織に無料枠を追加するのを待つために 1-2 日待つ必要があります。この間、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="互換性の問題"]
Microsoft 公式ドキュメント では、Microsoft が公式にメンテナンスしている 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 を使用してコードをコンパイルできます。

  • このステップでは、ほとんどの場合、msbuildLocationMethodversion に設定し、msbuildVersionlatest または他の指定バージョンに設定するだけで済みます。しかし、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 のような形式の日時を取得しましたが、4 番目の revision number が欠けています。非常に偶然なことに、Azure Pipelines は各 CI/CD タスクにバージョン番号を作成し、その形式は 20230228.1 のようになります。実際、小数点以下の数字はその日付の下でのタスクの回数を示しており、この数字は revision number に非常に適しています。なぜなら、バージョン番号が必ず順序的に増加することを保証できるからです。

Azure DevOps Marketplace では、GetRevision という名前のプラグインを追加できます。これを使用すると、以下のスクリプトを追加して rev_number 変数に revision 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

Pipelines YAML ファイルエディタに戻り、pfx 証明書のパスワードをこのパイプラインタスク内の変数として保存する必要があります。右上の Variables をクリックし、追加をクリックします。変数の追加ウィンドウで、変数に名前を設定し、Value にパスワードを入力し、最後に Keep this value secret にチェックを入れます。
pfx password variable

これで、Microsoft の公式 MsixSigning プラグインを使用して MSIX アプリパッケージに署名できます。

  • ここで certificateSecure 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

対応するコミットでは、対応する CI/CD タスクの情報も見ることができます。
Commit information

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。