GitHub Actionsで定数値などを各jobで使いまわす

# TL;DR

GitHub Actionsで多くのジョブを定義した際に、定数値（使うPythonバージョンやバッジの色だったりなど）などの定義を各ジョブで使いまわしたい（記述を統一したい）と思い色々調べて調整したのでその備忘録です。

# 発生していた問題点

少し前にGitHub Actionsでジョブを細かく分割・キャッシュ・並列化などしたのですが、その影響で各ジョブで重複する値の指定の部分が結構発生していました。

同一のジョブ内であれば環境変数など設定すれば問題ないのですが、ジョブをまたいだ場合には環境変数が反映されません。しかし記述が重複しているとDRY原則的に少々気になってしまいます。

できれば各ジョブの前に定数定義的なことができると良いな・・・という状態でした。

# 定数的に他のジョブの値を参照する方法

定数的に値を設定してその値を他のジョブで参照するには以下のような手順が必要になります。

- 必要な定数値などを設定するためのジョブでoutputsの定義を行う。
- その定数値などを参照したいジョブではneedsでそのジョブを指定する。
- needsが指定されていると`needs.<ジョブ名>.outputs.<定数などの名前>`という書き方でアクセスができる。

それぞれ順番にみていきます。

## 必要な定数値などを設定するためのジョブでoutputsの定義を行う

必要な定数値などを他のジョブで参照するために設定する場合にはoutputsの定義をジョブに加えます。

例えば`SetGlobalConstants`というジョブ名で`PYTHON_38_VERSION`といったようなPythonバージョンの定義であったり`PASSING_BADGE_COLOR`といったようなバッジ用の色の定義などをしたい場合には以下のような感じになります。

```yaml
jobs:
  ...

  SetGlobalConstants:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    outputs:
      PYTHON_36_VERSION: 3.6.15
      PYTHON_37_VERSION: 3.7.12
      PYTHON_38_VERSION: 3.8.12
      PYTHON_39_VERSION: 3.9.9
      PYTHON_310_VERSION: 3.10.0
      CHECKING_BADGE_COLOR: FFAA00
      PASSING_BADGE_COLOR: 0088FF
    steps:
      - run: echo 'Setting constans.'
  ...
```

※steps部分は必須なので最低限の出力だけしています。他の処理と一緒のジョブ内で設定する場合などにはこの辺の出力はスキップ可です。

## 他のジョブでneedsの指定や設定した定数値などを参照する

定義した値を他のジョブから参照したい場合にはまずneeds部分にその定数などを定義したジョブを含めます。

例えば定数定義を`SetGlobalConstants`というジョブでやっていれば以下のような感じになります。

```yaml
...
  RunFlake8:
    needs: [CreateCache, SetGlobalConstants]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      ...
```

このようにneedsの指定をしたジョブ内では`needs.<ジョブ名>.outputs.<定数などの名前>`といった記述でoutputsで指定していた定数にアクセスすることができます。

たとえば`SetGlobalConstants`というジョブ名で定数名が`PYTHON_36_VERSION`という名前であれば以下のように書くことで対象の定数にアクセスすることができます。

```yaml
  RunFlake8:
    needs: [CreateCache, SetGlobalConstants]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup the Python dependencies
        uses: ./.github/actions/setup_py_dependencies
        with:
          python-version: '${{ needs.SetGlobalConstants.outputs.PYTHON_36_VERSION }}'
```

なお、ジョブ1 -> ジョブ2 -> ジョブ3...といったようにジョブのフローが流れるようにしていてジョブ1で定数などを定義してジョブ3でそれらを扱いたい・・・といった場合でもneedsにはジョブ1を指定しないとエラーになります（ジョブ2のneedsでジョブ1が指定されている場合でもneedsの参照は引き継がれません）。

つまりジョブ3をスタートがジョブ2の次としていて、且つジョブ1の定数値を参照したいような場合にはジョブ3のneedsにはジョブ1とジョブ2を両方含める必要があります。

## 定数値をそのジョブ内の値で設定したい場合

定数値が固定ではなくジョブの実行結果次第で変わるようなケースでは、outputs内で`steps.<対象のstepのid>.outputs.<対象の値の名称>`とすることでジョブのsteps内の値にアクセスができるのでそれをoutputsの箇所で指定すれば対応ができます。

例えば値を設定するstepで`set-coverage`というidを付け、且つ`coverage`という名前で値が設定されている場合には以下のようにoutputsには書きます（定数名は`COVERAGE`としています）。

```yaml
  ...
    runs-on: ubuntu-latest
    timeout-minutes: 10
    outputs:
      COVERAGE: ${{ steps.set-coverage.outputs.coverage }}
```

対象のstep部分では以下のように書いています。`::set-output name=<値の名前>::<設定する値>`という書き方で値が設定できるようです。

```yaml
      ...
      - name: Set the coverage to outputs value
        id: set-coverage
        run: echo '::set-output name=coverage::${{ env.COVERAGE }}'
      ...
```

サンプルとして該当のジョブ全体では以下のような感じになっています。

```yaml
  RunTestsOnPython36AndSaveCoverageAndPassedNum:
    needs: [CreateCache, SetGlobalConstants]
    runs-on: ubuntu-latest
    timeout-minutes: 10
    outputs:
      COVERAGE: ${{ steps.set-coverage.outputs.coverage }}
      PASSED_TESTS_NUM: ${{ steps.set-passed-tests-num.outputs.passed-tests-num }}
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup the Python dependencies
        uses: ./.github/actions/setup_py_dependencies
        with:
          python-version: ${{ needs.SetGlobalConstants.outputs.PYTHON_36_VERSION }}
      - name: Set the pytest alias
        run: alias pytest=/opt/hostedtoolcache/Python/${{ needs.SetGlobalConstants.outputs.PYTHON_36_VERSION }}/x64/bin/pytest
      - name: Run the test runner command
        run: python run_tests_and_save_coverage_and_num.py
      - name: Set the environment variables from .env file
        uses: c-py/action-dotenv-to-setenv@v3
        with:
          env-file: .env
      - name: Set the coverage to outputs value
        id: set-coverage
        run: echo '::set-output name=coverage::${{ env.COVERAGE }}'
      - name: Set the passed tests number to outputs value
        id: set-passed-tests-num
        run: echo '::set-output name=passed-tests-num::${{ env.PASSED_TESTS_NUM }}'
```

# 実際のYAMLファイル

※今回の定数値などを利用する形にしたPythonプロジェクト用のワークフローのYAMLファイルのリンクを貼っておきます。日々更新を入れているので記事の内容と将来ずれてくるとは思いますがご了承ください。

https://github.com/simon-ritchie/apysc/blob/main/.github/workflows/deploy_to_pypi.yml

# 参考サイト

https://zenn.dev/dehio3/articles/4e9ef9a2d87ee5

https://docs.github.com/ja/actions/learn-github-actions/workflow-syntax-for-github-actions