GitHub Actions で uv を使うための備忘録

# はじめに

GitHub Actions を用いて、PR作成時等に `pytest` と `Ruff` を実行することで、開発サイクルに単体テスト実行とフォーマットを組み込みました。その際に開発環境と同様に、`uv` をパッケージ管理ツールを使いました。基本的には `uv` のドキュメントに沿って動かすことができましたが、追記した箇所が一部あるため、備忘録を兼ねて記事として共有します。


# Using uv in GitHub Actions

まずは、`uv` の公式ドキュメントに沿って、GitHub Actions 上で `uv` を利用できる状態を実現しました。

https://docs.astral.sh/uv/guides/integration/github/#syncing-and-running

```yml
name: Example

jobs:
  uv-example:
    name: python
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v4
```

# pytest
続いて、上記の `yml` をベースに `pytest` を実行する job を作成します。参考として、`pip` を用いて、`pytest` を GitHub Actions を実行する方法は、下記のドキュメントに記載されています。

https://docs.github.com/ja/actions/use-cases-and-examples/building-and-testing/building-and-testing-python

次のような `yml` ファイルを作成して、GitHub Actioons を実行してみました。すると、自作のパッケージが読み込めずテスト実行できないというエラーが発生してしまいました。

```yml
name: Example

jobs:
  uv-example:
    name: python
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version-file: "pyproject.toml"

      - name: Install the project
        run: uv sync --all-extras --dev

      - name: Run tests
        run: uv run pytest tests
```

この問題は `env` に `PYTHONPATH: ${{ github.workspace }}` の1行追加するだけで解決しました。`${{ github.workspace }}` はGitHub Actionsのコンテキスト変数で、現在のリポジトリのクローンがチェックアウトされるディレクトリを指します。リポジトリ内のPythonモジュールやパッケージをインポートして使用することが可能になります。`PYTHONPATH` を設定することで、これは uv に起因する問題ではないですが、躓いたので記載しました。

```yml
name: Example

jobs:
  uv-example:
    name: python
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Install uv
        uses: astral-sh/setup-uv@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version-file: "pyproject.toml"

      - name: Install the project
        run: uv sync --all-extras --dev

      - name: Run tests
        env:
          PYTHONPATH: ${{ github.workspace }}
        run: uv run pytest tests
```

これにより、下図のように `pytest` が実行できるようになりました。

![スクリーンショット 2024-12-16 16.57.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2065133/42da16f4-e368-6251-1a6a-67c183bd4c75.png)

# Ruff

Ruff についても、GitHub Docs の下記の例を参考にしました。

```yml
steps:
- uses: actions/checkout@v4
- name: Set up Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.x'
- name: Install the code linting and formatting tool Ruff
  run: pipx install ruff
- name: Lint code with Ruff
  run: ruff check --output-format=github --target-version=py39
- name: Check code formatting with Ruff
  run: ruff format --diff --target-version=py39
  continue-on-error: true
```

https://docs.github.com/ja/actions/use-cases-and-examples/building-and-testing/building-and-testing-python

上記の例をもとに、uv を使用する下記の記述に書き直してみました。

```yml
steps:
  - name: Check out repository code
    uses: actions/checkout@v4

  - name: Install uv
    uses: astral-sh/setup-uv@v4

  - name: Set up Python
    uses: actions/setup-python@v5
    with:
      python-version-file: "pyproject.toml"

  - name: Install ruff
    run: uv pip install --system ruff

  - name: Lint code with Ruff
    run: ruff check --output-format=github --target-version=py313

  - name: Check code formatting with Ruff
    run: ruff format --diff --target-version=py313
    continue-on-error: true
```

これにより、下図のように `Ruff` が実行できるようになりました。

![スクリーンショット 2024-12-16 17.04.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2065133/bef6d35b-e889-6513-e8ba-6979e709db10.png)

# その他の工夫

ここまでの設定で実行はできるようになりましたが、効率的なリソース活用のために、追加で2点の工夫をしたので、その内容についても記載します。

## 重複実行回避

次の設定をすることで、同じワークフロー名とリファレンスを持つワークフローが同時に実行されないようにしました。

```yml
concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: true
```

## タイムアウト設定

GitHub Actions では、デフォルトのタイムアウト時間が6時間になっているので、時間を明示的に5分まで短く設定しました。

```yml
jobs:
    test:
        runs-on: ubuntu-latest
        timeout-minutes: 5
```

# おわりに

以上の作業を経て、下記の2つの `yml` ファイルを作成しました。GitHub Actions で `uv` を動かす方の参考になれば幸いです。もし改善点等あればコメントください。

```pytest.yml
name: pytest

on:
    push:
        branches:
        - main
    pull_request:
        branches:
        - main

concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: true

jobs:
    test:
        runs-on: ubuntu-latest
        timeout-minutes: 5

        steps:
        - name: Check out repository code
          uses: actions/checkout@v4

        - name: Install uv
          uses: astral-sh/setup-uv@v4

        - name: Set up Python
          uses: actions/setup-python@v5
          with:
            python-version-file: "pyproject.toml"
              
        - name: Install the project
          run: uv sync --all-extras --dev

        - name: Run tests
          env:
              PYTHONPATH: ${{ github.workspace }}
          run: uv run pytest tests
```
```ruff.yml
name: ruff

on:
    push:
        branches:
        - main
    pull_request:
        branches:
        - main

concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: true

jobs:
    lint:
        runs-on: ubuntu-latest
        timeout-minutes: 5

        steps:
          - name: Check out repository code
            uses: actions/checkout@v4
  
          - name: Install uv
            uses: astral-sh/setup-uv@v4

          - name: Set up Python
            uses: actions/setup-python@v5
            with:
              python-version-file: "pyproject.toml"

          - name: Install ruff
            run: uv pip install --system ruff

          - name: Lint code with Ruff
            run: ruff check --output-format=github --target-version=py313

          - name: Check code formatting with Ruff
            run: ruff format --diff --target-version=py313
            continue-on-error: true
```