Azure DevOps ビルドパイプラインでテキストファイルの内容を読み取って変数にセットし、他のタスクで使う

例えばシナリオとして、「GitHub のソースコードをビルドして、GitHub の Releases にリリースするときに、バージョン番号をファイルから取得してタグやタイトルに使用したい」というような場合を考えます。

![概要 Text → GitHub git → Pipeline variables → GitHub Releases title, tag ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/e4737281-ba4a-e268-2040-a4498a4a0b0c.png)


GitHubRelease タスクには Tag や Release title が指定できるので「```ver.$(version)```」のように指定して「ver.1.0.2.3」のように展開されると嬉しいです。リリースノートは Release notes file path で指定できるので、Markdownファイルに書いておいてそのパスを指定することができますが、タグとタイトルはファイルのテキストそのままではなく、他の文字と連結したテキストにしたいところです。

## パイプラインに必要な手順
- リポジトリにテキストファイルを用意する（パスを確認しておく）
- 変数の名前を宣言する
- テキストファイルを読み込んで変数にセットする
  - File Content To Variable 拡張機能 を使う方法
  - PowerShell タスクで行う方法
- 変数を使用する

ビルドパイプラインでタスクを超えて変数を共有するのはこんな感じで出来ます。テキストファイルを読み取るのは PowerShell でも簡単にできますが、変数に値をセットするのがちょっと特殊なので、簡単にやりたい場合は拡張機能を使用するのが便利です。

## File Content To Variable 拡張機能 を使う方法（Classic）
クラシックパイプラインを使用しているチームなら、拡張機能を使うのがわかりやすく最もお勧めです。

事前に Marketplace で拡張機能 [File Content To Variable](https://marketplace.visualstudio.com/items?itemName=maikvandergaag.maikvandergaag-file-to-variable) をインストールしておきます。

クラシックパイプラインの「Variables」の画面で新しい変数（この例では version ）を追加します。

![Classic パイプライン 変数の宣言](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/7dea226f-4646-cbb4-0810-38da7b42b545.png)

「Tasks」の画面で File Content To Variable タスクを追加し、読み取りたいファイルと変数名を指定します。

また Add New Lines Characters のチェックを外します。改行が自動的に追加されるオプションですが、既定でチェックされているので注意が必要です。

![Classic パイプライン File Content To Variable 拡張機能の設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/bc46bef1-df36-bde9-6099-6f1577809fa9.png)

最後に使用したいタスクで変数を ```$(名前)``` の形式で指定します（この例では ```$(version)```  ）。

![Classic パイプライン 変数の使用](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/85c4f15a-f965-9e6e-e5cf-945f45ce037e.png)

変数は読み取りだけが可能です。と言うのも、この変数は、実行時に ```$(名前)``` の部分が変数の値に置き換えられる「マクロ」として動作しています。そのため、この変数に直接値をセットするような書き方はできません。


## File Content To Variable 拡張機能 を使う方法（YAML）

YAMLパイプラインだとこんな感じです。シンプルなので、クラシックエディタから YAML 表示させても、YAML のタスクで拡張機能を検索して追加しても、どちらでも簡単です。

```YAML
pool:
  name: Azure Pipelines
  vmImage: "windows-latest"

variables:
  version: ''

steps:

- task: FileContenttoVariable@2
  displayName: 'File content to variable version'
  inputs:
    FilePath: SampleFolder/Version.txt
    VariableName: version
    AddNewlines: false

- script: |
   echo $(version)
   

  displayName: 'See variable'
```

新たに追加する場合は、ファイル名と変数名を指定すればOKです。（AddNewlines は省略しても false になるので大丈夫なはずです）

もし、クラシックエディタで YAML をエクスポートして、

```
#Your build pipeline references an undefined variable named ‘version’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See 
```

こんなメッセージが YAML に含まれていたら、「Variables」の画面で変数の宣言を忘れています。YAMLの ```variables:``` が空になっているはずなので、変数名を追加すればOKです。


## PowerShell を使う方法（YAML）
拡張機能が使用できない場合や、PowerShell で書いた方が早いぜ！っていうチームでは、PowerShell タスクでファイルの値を読み取って、変数にセットします。単純なテキストファイルでなく、JSONやXMLなど、テキストの一部を読み取りたい場合もこの方法が有効です。

```YAML
pool:
  name: Azure Pipelines
  vmImage: "windows-latest"

variables:
  version: ''

steps:

- powershell: |
   $version = Get-Content -Path Version.txt  -Raw
   echo $version
   
   Write-Host "##vso[task.setvariable variable=version]$version"
   

  workingDirectory: SampleFolder
  displayName: 'Read Text File'

- script: |
   echo $(version)
   

  displayName: 'See variable'
```


1. リポジトリのテキストファイルのパスを確認（例：SampleFolder/version.txt）
2. variables に変数の名前を宣言する（例：version）
3. PowerShell タスクで
   - （必要に応じて）作業フォルダを設定（例：SampleFolder）
   -  Get-Content でファイルを読み取る 
   -  Write-Host ```"##vso[task.setvariable variable=名前]値"``` で変数を書き込む  
4. 他のタスクでは  ```$(名前)``` で使用できる（ 例：```$(version)``` ）

ポイントは変数を使用するときの表記 ```$(名前)``` は、いわゆるマクロであり、タスクの実行直前に文字列が置換して動作しているということです。つまりこの書き方では値を参照することはできても、セットすることができない。これをついつい忘れがちです。

代わりに専用の書式で Write-Host に出力すると、変数の値が更新されます。

参考：[Define variables - Set variables in scripts](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#set-variables-in-scripts)

この例ではテキストファイルの内容をそのまま使用しているので、Get-Content で読み取った値をそのまま変数に設定しています。JSON や XML など必要に応じて、テキストファイルの内容の一部を使用するに加工する処理を追加できます。


## PowerShell を使う方法（Classic）
クラシックパイプラインでも YAML と同様の設定ができます。

![Classic パイプライン 変数の宣言](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/cf14e039-ea85-e5e5-4a0c-95eff4ee3d40.png)

PowerShell タスクは、Write-Host ```"##vso[task.setvariable variable=名前]値"``` を覚えていられるか？がポイントなので、正直、クラシックパイプラインの UI でやるより YAML の方が良いですよね、リポジトリに保存されるので・・・

![Classic パイプライン PowerShell の設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/e1a41b00-a8e1-3640-eaba-e3097674a68f.png)

変数を使うのも同様です。

![Classic パイプライン 変数の使用](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474869/a75b64c1-4056-884c-d320-b11f6e253e26.png)


## お勧めは
私の個人的な感覚では、初めてパイプラインを使用するチームには、「Classic + File Content To Variable 拡張機能」がお勧めです。変数をパイプラインで使えるという感覚を身に着けるまで、直観的な UI のサポートを受けられるのは理解がとても早いように思います。

いわゆる「クラシック」なのであまり声を大きくして勧めたくないのですが、それでも、何より、現場のエンジニアだけでなく、その上長（技術に詳しくない管理職の人）や顧客にも、この画面は見せて意味が通じるので、とてもありがたいんですよね。画面を見せながらデモするだけでだいたい何をしているのか理解してもらえます。

その後、チームがパイプラインに慣れてきて、参考にできる YAML のパイプラインが沢山保存されている状況になれば、断然「YAML + PowerShell」で。もちろん、テキストファイルの一部を取得する要件があるとき、あるいは拡張機能のインストールができない環境では選択肢がありません。