GitHub Actions の Workflow 内でグローバル変数を使う（RUN 間の変数渡し・共有）

:::note warn
2022/10/11 この記事で紹介している `set-output` は廃止（`deprecated`）されることが決定し、近々使えなくなることがアナウンスされました。

本来、`set-output` は「アクション毎の環境変数」を保持し、どのアクションが設定したのか明確にするためのものでした。しかし、ユーザーが知らない間にアクション間で意図しない値をやりとりされる可能性もあったことから、`GITHUB_OUTPUT` に変数定義を追記する形に統一するそうです。

- [GitHub Actions: Deprecating save-state and set-output commands](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) @ Github.blog

```yaml:before
- name: Save state
  run: echo "::save-state name={name}::{value}"

- name: Set output
  run: echo "::set-output name={name}::{value}"
```

```yaml:after
- name: Save state
  run: echo "{name}={value}" >> $GITHUB_STATE

- name: Set output
  run: echo "{name}={value}" >> $GITHUB_OUTPUT
```

:::

---

# **`run` 内でグローバル変数をセットして他の `run` で使いまわしたい**。

> GitHub Actions の Workflow で、`env` ではなく、異なる `run` ステップ間で変数を共有したい。

```yaml:変数がrunステップ間で引き継がれない例
name: Sample Workflow

on:
  pull_request:

jobs:
  sample_job:
    runs-on: ubuntu-latest
    steps:
      - name: Step1
        run: |
          FOO='bar'
          echo "The FOO is ${FOO}"
          : # 結果: The FOO is bar
      - name: Step2
        run: |
          echo "The FOO is ${FOO}"
          : # 結果: The FOO is
          : # $FOO は引き継がれない
```

## TL; DR <sub><sup><sub><sup>（今北産業）</sup></sub></sup></sub>

1. 別ステップへの変数渡しには大きく 2 種類がある。
    1. ワークフロー全体の「環境変数」経由：
        - `GITHUB_ENV` に変数を追加セット（追記）する
    2. 各々のステップの「公開変数」経由：
        - `::set-output` でセットされた変数

1. ワークフロー全体の「環境変数」渡しの場合：
    - `GITHUB_ENV` に追記する。
        - Set: `run: echo "<name>=<value>" >> $GITHUB_ENV`
        - Get: `${{ env.<name> }}`
    - 一番楽。
        - <sub><sup>後日、[汎用化する](https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#example-reusable-workflow)可能性が「ない」場合に使う。**他と変数名がバッティング（重複）する可能性がある**ため。<br>アクション化やコピペピピックな使い方など、汎用的に利用する可能性がある場合は、`env` への書き込みは推奨されない。どのアクションで設定された値か、ユーザがトレースしにくくなるため。その場合、`env` 値は参照のみに限定して、次項の「ステップのグローバル変数」を使う。</sup></sub>
2. 各々のステップの「公開変数」渡しの場合：
    - 一番安全（変数名がバッティングしないため再利用性が高い）
    - `run` で `::set-output` を使う。
        1. ステップ内で `id` を宣言する。（ステップ ID の設定）
        1. 同ステップの `run` 内で「`::set-output`」と「変数」を `echo` して値を設定する。
        1. 他のステップで `id` を参照すると同じ値が使える。

    ```bash:idの構文
    id: <ステップ ID>
    ```

    ```bash:set-outputの構文（パラメーター）
    ::set-output name=<変数名>::<値>
    ```

    ```bash:呼び出し時の構文
    ${{ steps.[設定した時点のステップID].outputs.[設定した変数名] }}
    ```

    ```bash:設定例（変数MY_VALUEに"fuga"がセットされる。ステップIDは"hoge"）
    - name: Step1
      id: hoge
      run: echo '::set-output name=MY_VALUE::fuga'
    ```

    ```bash:呼び出し例（ステップID"hoge"の変数MY_VALUEを取得。ステップのnameが違うことに注目）
    - name: Step2
      # $value1 には 'fuga' が入る
      run: value1=${{ steps.hoge.outputs.MY_VALUE }}
    ```

    ```yaml:使用例
    ...
         - name: Step1 Set FOO
           run: |
               hoge='bar'
               echo "::set-output name=FOO::${hoge}"
           id: step-foo
         - name: Step2 Get FOO
           # 結果: The FOO is bar
           run: echo 'The FOO is' ${{ steps.step-foo.outputs.FOO }}
    ...
    ```

## TS; DR <sub><sup><sub><sup>（`RUN` の変数を理解するまでのコマケーこと）</sup></sub></sup></sub>

Workflow の `run` ステップで定義された変数は、たとえ `export` しても続く他の `run` ステップでは使えません。Docker の `RUN` ディレクティブ（指示子）と似た動きをします。

確かに、**`env` を使えば環境変数として定義できます**。つまり、どの `run` ステップでも使える変数を設定できます。

```yaml:あらかじめわかっている値なら環境変数が良い
name: Sample Workflow

on:
  pull_request:

env:
  HOGE_FUGA: Piyo

jobs:
  sample_job:
    runs-on: ubuntu-latest
    env:
      FOO_BAR: Buzz
    steps:
      - name: Step1
        run: |
          echo "The HOGE is ${{ env.HOGE_FUGA }}"
          : # Output: The HOGE is Piyo
      - name: Step2
        run: |
          echo "The FOO is ${{ env.FOO_BAR }}"
          : # Output: The FOO is Buzz
```

しかし、このパターンで `env` 文を使う場合、あらかじめわかっている値しか代入できません。例えば「ステップの中で日付をセットしたい」といったことができません。

実は、この `env` 文は、環境変数の `GITHUB_ENV` に追記する役割があります。

`GITHUB_ENV` は「シェル配列」となっており、1 行 1 変数定義で構成されています。

そのため、以下の様に変数定義を改行付きで `GITHUB_ENV` に（`>>` で）追加すると、各ステップで取得した情報をセットして利用できます。

```yaml:あらかじめわかっている値なら環境変数がらく
name: Sample Workflow

on:
  pull_request:

jobs:
  sample_job:
    runs-on: ubuntu-latest
    steps:
      - name: Step1
        run: |
          echo "HOGE_FUGA=Piyo" >> $GITHUB_ENV
          : # 改行付き echo であることに注目
      - name: Step2
        run: |
          echo "The HOGE is ${{ env.HOGE_FUGA }}"
          : # Output: The HOGE is Piyo
```

- 参考文献:
    - [コメント](#comment-bd2a6ea5524b5a978fe6)
    - [Setting an environment variable](https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#setting-an-environment-variable) | Actions | Docs @ GitHub

通常は、これが一番楽な設定です。

しかし、**どのステップでも書き換えることができる**ため、逆に言えば「どのステップで追加・編集・削除されたか」も不透明になります。

そのワークフローを見れば一目瞭然なのですが、[ワークフローを同じリポジトリ内の別のワークフローで再利用する](https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#example-reusable-workflow)場合や、[アクションとして公開する](https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace)場合に、追跡するのが大変になります。

「特定のステップで作成された変数が使いたい」と調べたところ、下記のように公式ドキュメントに記載がありました。

> You can use the `set-output` command in your workflow to set the same value:
>
> ```yaml:sample.yaml
>      - name: Set selected color
>        run: echo '::set-output name=SELECTED_COLOR::green'
>        id: random-color-generator
>      - name: Get color
>        run: echo 'The selected color is' ${steps.random-color-generator.outputs.SELECTED_COLOR}
> ```
>
> <sup>（ [Using workflow commands to access toolkit functions](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions) | Workflow commands for GitHub Actions @ GitHub Docs より）</sup>
>
> 【筆者訳】
> `run` ステップ内で `set-output` コマンドを使うと、同じ値を他のステップでも使えるようになります。
>
> ```yaml:sample.yaml
>      - name: Set Foo
>        run: |
>            hoge='bar'
>            echo "::set-output name=FOO::${hoge}"
>        id: step-foo
>      - name: Get Foo
>        # 結果: The FOO is bar
>        run: echo 'The FOO is' ${{ steps.step-foo.outputs.FOO }}
> ```
>
> ※ 筆者注） 上記の訳の YAML はわかりやすいように変更、および原文の YAML の typo を修正しています。

:::note info
実は、この原文には <a href="https://ja.wikipedia.org/wiki/%E3%82%BF%E3%82%A4%E3%83%9D" target="_blank">typo</a> があるようで  <code>${steps.random....COLOR} </code> では動かず、 <code>${{ steps.random....COLOR }}</code> と二重の <code>${{ ... }}</code>（カギ括弧）で囲う必要があります。
:::

### GitHub Actions で実行した日付を取得する

`echo` + `::set-output key=value::` の仕組みを応用すると日付も取得できます。イメージ作成やビルドなどには便利ではないでしょうか。

```yaml:Dockerのイメージ作成時の"--build-arg"オプションで、TAG_BUILDの値に日付を渡す例
      - name: Get current date (YYMMDD)
        id: date
        run: echo "::set-output name=today::$(date +'%Y%m%d')"

      ...

      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            keinos/helloworld:latest
          build-args: |
            TAG_BUILD=Build: ${{ steps.date.outputs.today }})

```

## 参考文献

- [Support global environment variables のコメント](https://github.community/t/support-global-environment-variables/16146/6?u=keinos) | GitHub Actions @ GitHub Community
- [Workflow commands for GitHub Actions](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions) @ GitHub Docs