AzurePipelineのパイプラインを作成し、中身を書いてみる

もくじ
https://tera1707.com/entry/2022/02/06/144447#Azure

やりたいこと

前回の記事で、AzurePipeline練習のための、AzureDevopsの登録、リポジトリの作成までやった。

今回は、Pileineを実際に作成し、中身の処理を書いて、ビルドするところくらいまでやりたい。

前提

  • 以下は、2023年5月時点で試したことのメモ。

やったこと

リポジトリにアプリのソリューションをpushする

前回作ったリポジトリに、アプリのコードをpushして、そいつのビルドをパイプラインで行ってみる。

今回は、WinUI3でテンプレートから精製したばかりの中身のないアプリをpushしてみた。 下記のような構造。

パイプラインの作成

「Pipeline」を押す。

最初、こういう画面になる。

「Create Pipeline」を押す。

Gitの種類を聞かれるので、

「Azure Repos Git」を選ぶ。

※今回は、AzureDevOps上のGitでやってみるので、それを選んだ。  「GitHub」を選べば、Githubで作ってるリポジトリでも、pipelineを組める様子。

リポジトリの一覧が出てくるので、

さっき作ったリポジトリ名を選ぶ。

※ここで、さきほど「GitHub」を選んでると、自分のGithubプロジェクトのアドレスを選ぶ画面になり、  選んだGirhubプロジェクトが持ってるリポジトリの一覧が出てくる。それを選べば、GithubリポジトリでPipeleineできるっぽい。(未検証)

pipelineの種類の選択が出てくるので、

.NET Desktopを選ぶ。(今回、WIinUI3アプリだが、それっぽい選択肢がなかったのでとりあえず)

これで、デフォルトのデスクトップアプリ向けのyamlが生成される。

yaml書く

pipelineをつくると、デフォルトのyamlリポジトリの先頭フォルダにできてた。

下記のような内容。

# .NET Desktop
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net

trigger:
- main

pool:
  vmImage: 'windows-latest'

variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'

steps:
- task: NuGetToolInstaller@1

- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

- task: VSTest@2
  inputs:
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

とりあえず、デフォyamlに書かれてる内容を理解してみる。

また少し+αになるような内容も調べてみる。(デバッグのための出力方法とか)

trigger:

trigger:
- main

上記のように書くと、「main」というブランチがpushされたら、pipelineが走る、という意味になる。

trigger:
- main
- develop/*

と書くと、

  • 「main」ブランチ
  • 「develop/」が先頭につくブランチ(develop/bugfixなど)

がpushされたら、パイプラインが走る、という意味になる。

trigger:
  branches:
    include:
    - main
    - releases/*
    exclude:
    - releases/old*

みたいに、複雑な指定もできる。(こういうブランチ名は走らせるけど、こういうブランチ名は走らせない、などを指定できる)

詳しくはこちら参照
https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/azure-repos-git?view=azure-devops&tabs=yaml#ci-triggers

pool:

pipeleine実行に使う仮想マシンを指定する

一覧はこれ

https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software

自分はWindowsだけ使えれば今はいいので、windows-latestでよさそう

No hosted parallelism has been purchased or granted. エラー

パイプラインをrunすると、

No hosted parallelism has been purchased or granted. 

というエラーがでて、パイプラインが止まってしまった。

これは、今のAzureパイプラインを利用するには、無料利用枠の登録というのが必要らしく、それを行っていないために出ている様子。

参考

https://rainbow-engine.com/azure-no-hosted-parallelism/

申請ページ

https://aka.ms/azpipelines-parallelism-request

フォームの記入の仕方

https://qiita.com/Hachiyou-Renge/items/2083f2ce9e8b38558805

無料で使えなくなった理由

https://learn.microsoft.com/ja-jp/azure/devops/release-notes/2021/sprint-184-update#azure-pipelines-1

https://devblogs.microsoft.com/devops/change-in-azure-pipelines-grant-for-private-projects/

23/04/月ころに、上記のページで、自分の作成したorganization「https://dev.azure.com/XXXXXX/」に対して、申請をした。2,3日後にメールがくるはず。

→次の日にメール来た。日本人からすると、怪しい海外メールのような見た目のメールが来た。

pipelineをrunしてみると、下記のような感じで、runできるようになった。

variables:

変数を定義する。
https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/variables?view=azure-pipelines

variables:
  solution: '**/PipelineTest.sln'
  buildPlatform: 'x64'
  buildConfiguration: 'Release'

上記では、「solution」という名前の「**/PipelineTest.sln」という文字列を定義している。他のも同じ。
使うときはこうする。

      - script: |
            echo $(solution)111111 # outputs my value
      - script: 
            echo ${{variables.solution}} # outputs my value

※ちなみに、**は「再帰的なワイルドカード」で、この場合だと、すべてのサブディレクトリ内のすべての .sln ファイルを検索する。
 *は「単一フォルダーのワイルドカード」。

また上記ページの例にあるように、

変数は、

  • パイプライン全体レベル
  • stageレベル
  • jobレベル

のように、適する階層に定義できる。

steps:

こちらのぺージにあるように、
https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started/key-pipelines-concepts?view=azure-devops

  • Pipeline
    • stage
      • job
        • task
        • task
      • job
        • task
    • stage
      • job
        • task
      • job
        • task

のような構造にできる。

- task: NuGetToolInstaller@1

nugetに必要なお作法っぽい。nugetでリストアとかするならとりあえず配置。

- task: NuGetCommand@2

リストアしたければとりあえず下記を書いとく。(下記はslnを渡してるが、csprojでも行けるっぽい)

- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'

- task: VSBuild@1

「slnをビルドするとき」は、VSBuildでよいらしい。
https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/msbuild-v1?view=azure-pipelines

「csprojをビルドするとき」は、MSBuildがよいらしい。
https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/vsbuild-v1?view=azure-pipelines

- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

デバッグ用変数出力

      steps:
      - script: |
            echo ${{variables.solution}} # outputs my value

もしくは

      - task: CmdLine@2
        displayName: echo2
        inputs:
          script: 'echo AAAAAAAAAAAAAAAA'

アーティファクトへの出力

ビルドした成果物を、azurepipelineのVMの中から取り出したい場合は、 「アーティファクト」という領域に成果物をコピーしてやらないといけない。

でないと、VMは毎回起動時にリセットされて、さらピンな環境に戻ってしまうので、毎回きれいに忘れてしまう。

アーティファクトに配置するには、下記のコマンドを使う。

      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: '$(Build.SourcesDirectory)\'
          artifact: 'MyArtifact'
          publishLocation: 'pipeline'

※ただし、これだとソースコード全部をartifactに含めてしまう。しかるべきフォルダだけをtargetPathに指定するように直す必要あり。

非パッケージアプリだと、これでartifactにフォルダごと置く、が最終出力でよさそう。

成果物をartifactに置くときには、

  • ソリューションのslnをVSBuildでビルドして、各プロジェクト(csprojなど)の下にできた成果物(bin)の下を、PublishPipelineArtifact@1でartifactに上げる
  • ソリューションのslnをVSBuildでビルドせず、プロジェクトのcsprojを個別にMSBuildでビルドする。その際、共通の出力先フォルダを指定しておき、できたものをPublishPipelineArtifact@1で一括でartifactに上げる

などのやり方が考えられる。(やったことあるのは後者の方。でも前者のほうが、パイプラインのスクリプトの量は減りそう。ソリューションの設定が少し面倒かもだが)

パッケージプロジェクトでパッケージを作る方法

パッケージアプリだと、VSでやってた「発行」のようなことをしないといけないかも?

パッケージを作るときのやり方参考。
https://qiita.com/okazuki/items/ef5f3357e2835be8fbdd

上記ではMSBuildを使ってるが、pipelineのタスク「VSBuild」で、wapprojを含むslnをビルドすれば、それだけでwapprojの下の「AppxPackage」フォルダに、msixができてそう。

       - task: VSBuild@1
         inputs:
           solution: '$(solution)'
           platform: '$(buildPlatform)'
           configuration: '$(buildConfiguration)'

MSBuildを使うときは、

.wapprojに対して、MSBuildでUapAppxPackageBuildModeをStoreUploadにしておけば、storeに挙げるためのパッケージができるっぽい。 →UapAppxPackageBuildModeを指定しなくても、下記でAppxPackageはできた。

       - task: MSBuild@1
         inputs:
           solution: '**/*.wapproj'
           msbuildVersion: '17.0'
           msbuildArchitecture: 'x64'
           platform: 'x64'
           configuration: 'Release'

※storeuploadだと、.msixuploadのファイルができる。パッケージのフォルダの中身自体は、全く同じだった。

.msixuploadのファイルがが必要であれば、UapAppxPackageBuildModeStoreUploadにしておけばよさそう。

パイプラインキャッシュでビルド時間を短縮する

今回は、20個以上のcsproj、vcsprojを含むソリューションをパイプラインでビルド時、nugetパッケージの解決にとにかく時間がかかるということがあったので、下記のページにあるように、nugetパッケージの解決にかかる時間を短縮するためにキャッシュを使った。

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/artifacts/caching-nuget?view=azure-devops

が、キャッシュ機能は、それ以外にも、次回ビルド時にも保存しておきたいものをおいておける様子。(未検証)

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/release/caching?view=azure-devops

下記はサンプルのコードの、キャッシュ部分を抜き出したもの。

  • Nugetツールインストールをして、
  • キャッシュを行い、
  • リストアをする
    • ただし、キャッシュがすでにリストアされてないときだけ

という流れで行う。

variables:
  NUGET_PACKAGES: $(Pipeline.Workspace)/.nuget/packages

steps:
- task: NuGetToolInstaller@1
  displayName: 'NuGet tool installer'

- task: Cache@2
  displayName: 'NuGet Cache'
  inputs:
    key: 'nuget | "$(Agent.OS)" | **/packages.lock.json,!**/bin/**,!**/obj/**'
    path: '$(NUGET_PACKAGES)'
    cacheHitVar: 'CACHE_RESTORED'

- task: NuGetCommand@2
  displayName: 'NuGet restore'
  condition: ne(variables.CACHE_RESTORED, true)
  inputs:
    command: 'restore'
    restoreSolution: '$(solution)'

CACHE_RESTOREDは、キャッシュがヒットしたとき(キャッシュを使ったとき)に、Cache@2タスクがtrueにしてくれるフラグ。

参考情報

AzurePipeline情報源まとめ

https://tera1707.com/entry/2023/05/22/222306