はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

AzureDevOpsのCIで.NET CoreとAngularのアプリケーションをバージョンアップする

はじめに

この記事内で使用する各フレームは下記バージョンで構成しています。

  • Angular: 9.1.8
  • .NET Core: 3.1

また、AzureDevOpsは記事作成時点のキャプチャやコマンドが掲載されています。

この記事を参照されるタイミングによっては

動作しなかったり画面が違うといった可能性がありますのでご留意くださいませ。

モチベーション

アプリケーションのバージョンアップ作業って地味で忘れがちです。

そういった作業は自動で行ってしまいたい!!

と、いうわけでAzure DevOpsのCIフローの中でバージョンアップ作業ができないか試してみました。

僕が主に使用しているのは.NET CoreとAngularのアプリケーションですので

その二つのアプリケーションでバージョンアップを実施するCIを組んでみようと思います。

.NETアプリケーション

.NETのアプリケーションのバージョンアップをCIで実現しようと思います。

.NETアプリケーションのバージョン番号はメジャー.マイナー.ビルド番号.リビジョンで表現されますが

ビルド番号、あるいはリビジョンに*指定で、ビルド時に自動的にバージョンアップをしてくれます。

なので普通に運用する場合は何も考えなくてもバージョンアップはしてくれるのですが

バージョン付番を独自のルールで運用したり、付番したバージョンをデータストアに格納したりとかしたい場合があります。

なので、今回は独自ルールでバージョンを付番してアプリケーションを発行するCIを構築してみようと思います。

バージョンアップルール

今回は下記のルールでバージョンアップしようと思います

  • 2020年6月2日からの経過日数をバージョン番号とする
  • ビルドを行った日の00:00からの経過分数をリビジョンとする

.NETアプリケーションのビルドコマンドでバージョン番号を指定できるよう準備

MsBuld.exeを使用してビルドする際のコマンドでカスタムプロパティを指定できます。

参考:MSBuild Properties1

そこで、ビルドコマンドのArgumentsでバージョンを変更できるようにします。

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UseWPF>true</UseWPF>
    <PublishSingleFile>true</PublishSingleFile>
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
    <!-- バージョン番号のベースの経過日数を受け取る -->
    <BuildNumber Condition=" '$(BuildNumber)' == '' ">0</BuildNumber>
    <!-- リビジョンのベースの経過分数を受け取る -->
    <Revision Condition=" '$(Revision)' == '' ">0</Revision>
    <!-- バージョンの指定-->
    <AssemblyVersion>0.0.$(BuildNumber).$(Revision)</AssemblyVersion>
    <VersionPrefix>0.0.$(BuildNumber).$(Revision)</VersionPrefix>
  </PropertyGroup>

</Project>

これで、MsBuild.exe <プロジェクト> /p:BuildNumber=<適当な数字> /p:Revision=<適当な数字>でバージョンをコマンド上で指定できるようになりました。

MsBuild.exe <プロジェクト> /p:BuildNumber=1 /p:Revision=2した結果は下図のとおりです

f:id:TakasDev:20200613115738p:plain

CIの設定

CIで使用する変数の設定

CIの複数のタスクでバージョン情報を使い回すので、その情報を格納する変数を指定します。

経過日数を格納する$daysと経過分数を格納する$dayintervalを用意しています。

variables:
  days: 0
  dayinterval: 0

Power Shell

まずはPowerShellでコマンドで指定するバージョン番号とリビジョンを生成しています。

参考:PowerShellタスク

現在日付をゴニョゴニョして、$days$dayinterval 変数に格納します。

  • 2020年6月2日からの経過日数をバージョン番号とする
  • ビルドを行った日の00:00からの経過分数をリビジョンとする

ってことをやっています。

- task: PowerShell@2
  displayName: 'Calc Build Version'
  inputs:
      targetType: inline
      script: |
        $baseDate = [datetime]"06/02/2020"
        $currentDate = $(Get-Date)
        $interval = NEW-TIMESPAN -Start $baseDate -End $currentDate
        $days = $interval.Days
        Write-Host "##vso[task.setvariable variable=days]$days"
        echo $days
        $today = Get-Date -f d
        $startdate = Get-Date $today
        $todayinterval = NEW-TIMESPAN -Start $startdate -End $currentDate
        $dayinterval = [Math]::Truncate($todayinterval.TotalMinutes)
        Write-Host "##vso[task.setvariable variable=dayinterval]$dayinterval"
        echo $dayinterval

Write-Host "##vso[task.setvariable variable=<変数名>]<PowerShell内の変数(突っ込みたいValue)>"で👆で作成した変数に突っ込めます。

このPowershellでは、👆で指定したdaysdayintervalに値を突っ込んでいますね。

バージョンの情報を管理しているサービスなんかがある場合は

WebAPIを作成して、Invoke-WebRequestコマンドを使用して引っ張ってきたり登録したりすればいいかなと思います。

MsBuild

MsBuildで👆で指定したバージョンを指定してビルドを行うコマンドを作成していきます。

参考:MsBuildタスク

- task: MSBuild@1
  inputs:
    solution: '**/<プロジェクト名>/**/*.csproj'
    configuration: Release
    msbuildArguments: /t:Publish /p:BuildNumber=$(days) /p:Revision=$(dayinterval)

これはそこまで複雑なことはしていないですね。

PowerShellで設定した変数をMsBuildで使用するカスタムプロパティに指定しています。

実行結果

PowerShellタスクで指定するバージョン情報を出力しています。

f:id:TakasDev:20200613134513p:plain

Publishされたファイルのバージョンが、Powershellで生成した情報で構成されていることが確認できました。

Angularアプリケーション

普通にWebアプリケーションを作成するだけであれば特に意識する必要はないと思うのですが

ライブラリを作成してPublicなりPrivateなnpmレジストリにPublishしたい場合、バージョンアップは必要な作業です。

基本はnpm versionコマンドでバージョンアップしていけば良いかなと思っています。

バージョンアップルール

今回は下記のルールでバージョンアップしようと思います

  • masterブランチのCIはnpm version patchでパッチバージョンを上げる
  • developブランチのCIのときはプレリリースでdevバージョンとしてリリースする

CIの設定

ちょっとCIの順番と連動していないのですが、行う作業の関連など考慮して順不同で説明しています。

Power Shell

npmコマンドをPowerShellで実行します。

このPowershellコマンドでは下記のことを実行します

  • npm versionの実行
    • ライブラリのpackage.jsonのバージョンを上げたいのでライブラリのディレクトリで行う
  • バージョン変更をリモートリポジトリにPushする
    • そうしないと次回以降も同一のバージョンが生成されてしまうためですね

参考: MS Doc - Run Git commands in a script

参考: MS Doc - Build Azure Repos Git or TFS Git repositories

参考: Stack Overflow - How to increase a version of an npm package using Azure Devops pipeline

先にyamlを掲載すると👇な感じになります。

- task: PowerShell@2
  displayName: 'Version Up & Commit'
  inputs:
    targetType: inline
    script: |
      git config user.name "learn-angular-library-ci Build Service (devtakas-public)"
      git config user.email "dummy@example.com"
      $BranchName = "$(Build.SourceBranch)" -replace "refs/heads/"
      git checkout $BranchName --quiet
      cd projects\sample-lib
      npm version $env:VERSIONCOMMAND --preid=dev -m "$env:VERSIONCOMMENT" --force --silent
      git add .
      git commit -m "$env:VERSIONCOMMENT"
      git push --quiet

Script内1~2行目

git config user.name "learn-angular-library-ci Build Service (devtakas-public)"
git config user.email "dummy@example.com"

gitにコミットするためにユーザーの情報などを設定しています。

ここは好きな値で問題ないです。

Script内3~4行目

$BranchName = "$(Build.SourceBranch)" -replace "refs/heads/"
git checkout $BranchName --quiet

Azure DevOpsのCI実行ログを見ればわかるのですが

ソースコードの操作が行われているブランチは厳密にはCIコマンドで指定したブランチと異なります。

f:id:TakasDev:20200614140458p:plain

バージョン変更を行ったコミットをPushしたいブランチはCIで指定するブランチなので

環境変数に格納されている作業ブランチをベースに作業ブランチのチェックアウトを行う必要があります。

Script内最後の行まで

cd projects\sample-lib
npm version $env:VERSIONCOMMAND --preid=dev -m "$env:VERSIONCOMMENT" --force --silent
git add .
git commit -m "$env:VERSIONCOMMENT"
git push --quiet

作業ディレクトリをライブラリのpackage.jsonがあるところまで移動して諸々作業します。

$env:VERSIONCOMMAND はAzure DevOpsのCI上で設定する変数です。

f:id:TakasDev:20200614141100p:plain

masterのCIではpatch、developのCIではprereleaseを指定しています。

AzureDevOpsのCIでは、rootディレクトリ以外ではnpm versionしてもCommitが発生しなかったので

別途 git add .git commitしています。

警告などでるとAzure DevOpsのCIでエラーを吐いてしまうので

適宜--quiet--silentでエラーにならないように回避しています。

参考: Stack Overflow - How to generate NPM release candidate version

参考: はらへり日記 - npm scriptsでエラーログを表示させたくない話

認証の設定

参考: MS Doc - Run Git commands in a script

AzureDevOpsで管理されているリポジトリに対してPushを行いたいので

ソースを取得する段階で認証情報を設定しちゃいます。

下記で設定可能です。

steps:
- checkout: self
  persistCredentials: true

Trigger

例えばdevelopブランチの変更があった場合、developブランチにCommit/Pushが行われます。

つまり、なにも考慮しないと無限ループになります(なった)。

自分は、単純に下記の通り自動変更される対象のファイルをexclude設定にしました。

trigger:
  branches:
    include:
    - develop
  paths:
    exclude:
    - projects/sample-lib/package.json

ここは人により設定が変わりそうなところですね。

ユーザーの設定

CIには実行するユーザーが割り振られています。

なのでそのユーザーがソースコードに対してCommitしたりPushできたりするように設定する必要があります。

参考: MS Doc - Run Git commands in a script

設定場所は下図から確認できます。

f:id:TakasDev:20200614142736p:plain

Usersの<プロジェクト名> Build Serviceという名前に基本なっていると思います。

設定は下記のとおりですね。

f:id:TakasDev:20200614143016p:plain

  • Contribute / Read
    • 必須
  • Create Branch / Create Tag
    • 必要があれば
    • リリース時にタグ付けしたいとか、自動コミットじゃなくてブランチとPR作りたいとかの場合かな?と思います
  • Bypass policies when pushing
    • ブランチポリシーで保護しているブランチに対してPushしたい時に必要です。

実行結果

今回はDevOps上のArtifactsにライブラリをPublishしました。

  • developブランチのCIの場合

f:id:TakasDev:20200614144429p:plain

preleaseで出力されていることが確認できます。

  • masterブランチのCIの場合

f:id:TakasDev:20200614145048p:plain

patchで出力されていることが確認できます。

おわりに

今回使用したリポジトリは下記の通りです。

Angularの方はブランチポリシー下の動作検証のためAzure DevOpsでソース管理もしています。

.NET Core

Angular

供養

長く苦しい戦いの記録

f:id:TakasDev:20200614150251p:plain