GitHub ActionsでX.Y.Z形式のバージョニング自動タグ付けを行う

# GitHub ActionsでX.Y.Z形式のバージョニング自動タグ付けを行う

こんにちは！
株式会社LIFULLの吉永です。
本日はGitHub Actionsで自動的にX.Y.Z形式のバージョニングのタグ付けを行う方法について紹介します。

## やりたいこと

先日関わっていたとあるプロジェクトにて、GitHub ActionsでのCI/CD環境の構築を行いました。
その際に、masterにマージされたら自動的にバージョンタグを付加して欲しいという要件があったので、その要件を満たすべくGitHub Actionsでの実現方法を調査していました。
なお、そのプロジェクトでは今まで手動にてバージョンタグを付加しており、X.Y.Z形式のいわゆる[セマンティック バージョニング](https://semver.org/lang/ja/)方式を採用していました。
なので、今回やりたいこととしては

1. 既存の最新タグを取得する。
1. 条件に応じて、メジャー、マイナー、パッチのいずれかをインクリメントする。
1. 最新のバージョン文字列でタグ付けを行う。

となります。

## 実現するまでの流れ

上司と上記要件を満たすべく色々ディスカッションしていたのですが、当初私が考案した方法は下記でした。

- メジャー、マイナーに関してはGitHubのsecretsに登録しておく。
よってメジャー、マイナーのインクリメントは人間の判断で行う。
- メジャー、マイナーが既存の最新タグと相違ないなら、パッチバージョンをインクリメントする。

といった感じでした。
しかし私の上司は非常に前向きな思考の方で、「これ、メジャーもマイナーも工夫して、Actions内の処理で自動的に判断してインクリメントできないかな？」と提案してくれました！
そして、上司と私とでメジャーをインクリメントする時はリポジトリ内のどのファイルが更新された、もしくは追加された時か？
同様にマイナーの時の条件は？というのをディスカッションし、今回のプロジェクトではこの方法で行こう！と二人で納得して自動インクリメントする方法を考えました。

## GitHub ActionsのYAML

そうして実際に出来上がったYAMLが下記です。
※実際にはもう少し処理がありますが、わかりやすいように自動タグ付け部分のみにフォーカスしました。

```yaml
name: Auto Tag

on:
  pull_request:
    branches:
      - master
    types: [closed]

jobs:
  production:
    if: github.ref == 'master' && github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Unshallow
        run: git fetch --prune --unshallow --tags
      - name: Get Next Version TAG
        id: get-next-version-tag
        run: |
          MajorVersion=`git describe --tags $(git rev-list --tags --max-count=1) | awk '{split($0, version, "."); print version[1]}'`
          MinorVersion=`git describe --tags $(git rev-list --tags --max-count=1) | awk '{split($0, version, "."); print version[2]}'`
          PatchVersion=`git describe --tags $(git rev-list --tags --max-count=1) | awk '{split($0, version, "."); print version[3]}'`
          CurrentVersion=`git describe --tags $(git rev-list --tags --max-count=1)`
          echo "::set-output name=tag::$(git diff --name-status $CurrentVersion origin/master -w --ignore-blank-lines | awk -v v1=$MajorVersion -v v2=$MinorVersion -v v3=$PatchVersion 'BEGIN{b1=0;b2=0;}{if($1=="A" || $1=="M"){if($2~/app\/views\/shared\//){b1++}else if($2~/app\/controllers\//){b2++}}}END{if(b1!=0){v1++;v2=0;v3=0;}else if(b2!=0){v2++;v3=0;}else{v3++}printf "%s.%s.%s\n",v1,v2,v3}')"
      - name: Tag snapshot
        uses: tvdias/github-tagger@v0.0.1
        with:
          repo-token: ${{ secrets.GH_ACCESS_TOKEN }}
          tag: ${{ steps.get-next-version-tag.outputs.tag }}
```

## 詳細解説

まず下記のコマンドで、現在のmasterブランチと最新のタグとの差分を抽出します。

```bash
git diff --name-status $CurrentVersion origin/master -w --ignore-blank-lines
```
これで差分のあるファイルの一覧を取得できるので、awkコマンドで差分情報を正規表現を使いながら判断していくといった流れになっています。
awkコマンドのスクリプト部分を見やすいようにインデントをつけると下記のようになります。

```awk
BEGIN
{
    b1=0;
    b2=0;
}
{
    # ここの処理は前段のgit diffで標準出力に出力された行数分だけ繰り返される
    if($1=="A" || $1=="M"){
        # 追加か更新があれば
        if($2~/app\/views\/shared\//){
            # /app/views/shared配下のファイルに追加更新があれば、メジャーバージョンを上げる
            b1++
        }else if($2~/app\/controllers\//){
            # /app/controllers配下のファイルに追加更新があれば、マイナーバージョンを上げる
            b2++
        }
    }
}
END
{
    if(b1!=0){
        # メジャーバージョンを上げる
        v1++;
        v2=0;
        v3=0;
    }else if(b2!=0){
        # マイナーバージョンを上げる
        v2++;
        v3=0;
    }else{
        # パッチバージョンを上げる
        v3++
    }
    printf "%s.%s.%s\n",v1,v2,v3
}
```
※なお、メジャー、マイナーのバージョンアップ判定時のフォルダはあくまでもイメージです。実際にはもう少し複雑な条件になっています。

もしご自身のプロジェクトに上記処理を組み込みたい場合はメジャーバージョンとマイナーバージョンを上げる条件式をご自身のプロジェクト用に変更することで、簡単に適用することが可能です。
※移植性を考慮すると、条件式内を変数化したり、配列などで複数条件にも対応させるなどした方が良いかもしれませんね！

## 最後に

さて、この記事でお伝えしたいこととしては、もちろん上記の自動バージョン付け処理を実現することで、より快適なCI/CD環境を構築できるようになることも紹介したかったのですが、実現するまでの流れのところでご紹介したように、チームで話し合って、より良いアイディアを模索する、諦めない、ということもお伝えしたいことです！
というのも、私は早々にメジャー、マイナーの自動アップデートは無理かと思って、パッチのみ自動で、と設計してしまったので・・・上司のように、「ちょっと待った、もうちょっと何かできることないかな？」と考えることが足りていなかったなぁという反省があります。
実は今回ご紹介したGitHub Actionsの処理の流れも、8割方上司の方が短時間で組み上げ、その処理に肉付けをしたのが私という感じでした・・・
[パレートの法則](https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E6%B3%95%E5%89%87)がこんなところにも・・・苦笑
ということで皆さんも、何かやりたいこと、実現したいことがあった場合に、手堅く、ささっとできることを優先するのではなく、少しだけチャレンジしてみる姿勢も大事にして欲しいなと思います。！
もちろん社会人なので、いたずらに時間を浪費してしまうのは悪ですが、かといってあまりにも効率を求め、無難な道ばかりを選択してしまうのも少しもったいないですよね！
会社にもよるとは思いますが、エンジニアにはある程度の裁量で、チャレンジすることを許容してくれている会社も多いと思うので、そういった会社に所属している方は是非その時間を有効活用するようにしてください！
まだ入って日は浅いですが、LIFULLはエンジニアの「内発的動機」を大事にしてくれており、チャレンジしやすい環境を提供してくれているなぁと実感しております！

といったところで、本日は以上となります。
最後までご覧いただきありがとうございました！
それではまた次の記事でお会いしましょう。