LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

GitHub ActionsでChangeLogからReleasesを自動化する

概要

ソフトウェア開発者 (ユーザ) は、ソフトウェアをリリースする際に幾つかの作業を実施する必要がある。 しかし、それらの作業は、ある程度決められた作業を実施する必要がある。 ヒューマンエラーやユーザの手間を考慮すると、これらの作業を自動化できるとうれしい。

本記事では、GitHubReleases機能を使用しているプロジェクトを対象とする。
Releases機能は、GitHub上にあるリポジトリと紐づけられていて、リポジトリのタグに対して任意のファイルやメッセージを残すことができる。

この機能を使うユースケースとして、下記の作業が考えられる。

  • ユーザは、ChangeLog(または、NEWSなど)に更新点を書き記す。
  • ユーザは、任意のコミットにタグを作成する。
  • ユーザは、GitHubにあるリモートリポジトリにタグをプッシュする。
  • ユーザは、該当リポジトリのパッケージを作成する。
  • ユーザは、Releases 機能からメッセージ(リリースノート)の追加や任意のファイル(パッケージ)を添付する。

本記事では、「パッケージを作成する」作業と「Releases 機能」における作業をGitHub Actionsを利用して自動化を目指す。*1
下記は、本記事を適用したときのソフトウェアのリリースするまでの手順を示したものである。

f:id:LeavaTail:20201024182743p:plain
Releasesするまで手順

  1. ユーザは、ChangeLogに更新点を書き記す。
  2. ユーザは、任意のコミットにタグを作成する。
  3. ユーザは、GitHubにあるリモートリポジトリにタグをプッシュする。
  4. GitHub Actionsは、Change Logからメッセージ(リリースノート)の追加とパッケージを生成し、添付する。

はじめに

本作業は下記のリポジトリのworkflowに導入済みである。本記事では、これを基に説明する。 github.com

リポジトリでは、automakeを使用しているため、下記のmakeターゲットがデフォルトで用意されている。

  • make: ソフトウェアをビルドする
  • make install: 成果物をユーザの環境にインストールする
  • make dist: 配布用パッケージを生成する (パッケージ名-バージョン名.tar.gz)

Git-flowは下記のような形式を採用しており、適当なタイミングでtag(図中の灰色吹き出し)を作成している。
このtagが作成されたタイミングで、そのバージョンにおけるパッケージとChangeLogを残すようにしている。

f:id:LeavaTail:20201024224042p:plain
本プロジェクトのGit-flow

また、図中の白吹き出しはブランチを表している。

  • mainブランチ: 製品として常に安定した状態を保つ。
  • bugfixブランチ: リリース後に、不具合を修正する。
  • developブランチ: 次のリリースに向けた作業をする。
  • featureブランチ: 各機能における開発をする。

自動化のための作業

Releases手順の自動化に向けて、あらかじめ以下の作業を完了させておく必要がある。

  • ChangeLogから変更内容を取得するスクリプトの作成
  • Releases 機能を自動的に設定するworkflowの作成

ChangeLogから変更内容を取得するスクリプトの作成

Releases のリリースノートに記述するための、該当バージョンの更新点をChangeLogから抽出する必要がある。

今回は、下記のようなフォーマットのChangeLogをユーザが作成している場合を考える。

# Changelog
## [1.1.0] - 2020-10-01
### Added
- 新機能C
- 新機能D

### Changed
- 既存機能Zの修正

### Removed
- 既存機能Yの削除

## [1.0.1] - 2020-09-11
### Fixed
- 既存機能Xの修正

## [1.0.0] - 2020-09-01
### Added
- 新機能A
- 新機能B

この形式は、第2レベルの見出しに[バージョン] - 日付が記述されていて、第3レベルの見出しに変更の種類(AddedやChanged)が記述されている。
リリースノートとして必要となるのは、該当バージョンにおける第2レベルの見出し以下の内容となる。

そのため、該当バージョンの第2レベルの見出しの検索とその内容を取得する必要がある。 第2レベルの見出しの検索は^##grepすることで取得できる。

    $ grep -n "^## " ChangeLog.md
    2## [1.1.0] - 2020-10-01
    13:## [1.0.1] - 2020-09-11
    17:## [1.0.0] - 2020-09-01

ここから、awkコマンドでパターンマッチを行い、該当バージョン(VERSION)内容の先頭と末尾の行番号を取得する。

    $ grep -n "^## " ChangeLog.md |\
        awk -F: -v version=${VERSION} '/'"${VERSION}"'/ \
            { start = $1 + 1; getline; end = $1 - 1 } \
            END { print start, end }' )
   sed -n ${sline},${eline}p ${FILE}

例えばv1.1.0の場合、先頭は3行目、末尾12行目が得られる。
ここから、ChangeLogの3行目から12行目を表示することで該当バージョン(v1.1.0)の変更内容を取得できる。

上記の内容を踏まえて、下記のようなChangeLogから変更内容を取得するスクリプトget_changelog.shを用意する。

#!/bin/bash
FILE=CHANGELOG.md
VER=`echo $1 | tr -d "refs/tags/"`    # i.e. v1.0, v20.15.10
VERSION=`echo ${VER} | tr -d v`       # i.e. 1.0, 20.15.10

read sline eline <<< \
    $( grep -n "^## " ${FILE} | \
   awk -F: -v version=${VERSION} '/'"${VERSION}"'/ \
      { start = $1 + 1; getline; end = $1 - 1 } \
      END { print start, end }' )
sed -n ${sline},${eline}p ${FILE}

Releases 機能を自動的に設定するworkflowの作成

タグのプッシュを契機に、Releases 機能を自動的に設定するworkflowの作成する必要がある。

GitHubでは、Releasesを生成するActions と ReleasesにファイルをアップロードするActionsが用意されている。

github.com github.com

create-releaseを利用して、Markdownファイルで記述されたリリースノート(body.md)からReleasesの作成する場合には下記のように記述する。 (Example workflowを参照)

- name: Create Release
  id: create_release
  uses: actions/create-release@v1
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 
  with:
    tag_name: ${{ github.ref }}                      
    release_name: Release ${{ github.ref }}      # Releasesのタイトル (i.e. Release v1.0.0)
    body_path: "body.md"                         # Releasesのリリースノート
    draft: false
    prerelease: false

upload-release-assetを利用して、ファイルをアップロードする場合も同様にExample workflowが用意されているのそれに従って記述する。

ただし、Automakeで生成される配布物パッケージ名はパッケージ名-バージョン名.tar.gzとなっており、可変のファイル名である。
upload-release-assetはアップロードするファイルの名前に正規表現をサポートしていないため、Example workflowに一工夫しなければいけない。

コミュニティで提案されているのは、前のステップにて該当ファイルを環境変数に代入する方法である。

- name: Get Name of Artifact
  run: |
    ARTIFACT_PATHNAME=$(ls debugfatfs-*.tar.gz | head -n 1)
    ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME)
    echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV
    echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV
- name: Upload Release Asset
  id: upload-release-asset
  uses: actions/upload-release-asset@v1
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  with:
    upload_url: ${{ steps.create_release.outputs.upload_url }}
    asset_path: ${{ env.ARTIFACT_PATHNAME }}
    asset_name: ${{ env.ARTIFACT_NAME }}
    asset_content_type: application/gzip

上記のActionsを用いて、Releases 機能を自動的に設定するworkflowを用意する。

name: Create Releases

on:
  push:
    tags:
      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: setup environment           # パッケージ生成のための環境構築
      run: |
        sudo apt-get update
        sudo apt-get install autoconf automake libtool help2man make
    - run:  script/bootstrap.sh
    - run: ./configure
    - run: make
    - run: make dist          # 配布物パッケージの生成 i.e. debugfatfs-0.1.0.tar.gz
      env:
        CI: true
    - run: |                  # 更新内容を一時的にbody.mdとして保存しておく
        ./get_changelog.sh ${{ github.ref }} > body.md
    - name: Create Release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 
      with:
        tag_name: ${{ github.ref }}                      
        release_name: Release ${{ github.ref }}      # Releasesのタイトル (i.e. Release v1.0.0)
        body_path: "body.md"                         # Releasesのリリースノート
        draft: false
        prerelease: false
    - name: Get Name of Artifact
      run: |
        ARTIFACT_PATHNAME=$(ls debugfatfs-*.tar.gz | head -n 1)      # 正規表現で成果物パッケージのファイル名を取得する
        ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME)
        echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV         # 成果物パッケージのファイル名の環境変数に設定する
        echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV
    - name: Upload Release Asset
      id: upload-release-asset
      uses: actions/upload-release-asset@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        upload_url: ${{ steps.create_release.outputs.upload_url }}
        asset_path: ${{ env.ARTIFACT_PATHNAME }}
        asset_name: ${{ env.ARTIFACT_NAME }}
        asset_content_type: application/gzip

実行例

# Changelog

## [0.1.0] - 2020-10-24

### Added

- Print main Boot Sector field
- Print any cluster
- Print Directory list
- Backup or restore FAT volume
- Convert into update latter
- Create file
- Remove file
- Change any FAT entry
- Change any allocation bitmap
- Trim deleted directory entry

## Initial Version
  • tag (v0.1.0)を作成し、リモートリポジトリにプッシュする。

     $ git tag v0.1.0
     $ git push origin --tags
    

f:id:LeavaTail:20201025072025p:plain
タグ (v0.1.0)をプッシュした後のリポジトリ

f:id:LeavaTail:20201025072301p:plain
Releases v0.1.0の概要

おわりに

本記事では、ユーザがタグを作成したタイミングでChangeLogからReleasesを自動的に生成するworkflowを作成した。

この手順では、ChangeLogの生成がユーザの手作業で書き記す必要があるが、制度の良いcommitを生成しているのであればこの作業も自動化しても良いだろう。 今回は、正規表現を利用してアップロードファイルを指定しているのでひと手間かかったが、GitHub Actionsは様々なworkflowが用意されているので、任意の作業の自動化が容易である。

このように作業の自動化が容易であるため、定型的な作業がGitHub Actions (などのサービス)を利用して可能な限り自動化していくとよい。

参考

*1:ChangeLogの生成を自動化に関しては「keep a changelog」に則り実施していない。