GitHub Actions CI/CD ワークフローを用いてパッケージ配布物のリリースを公開する#

GitHub Actions CI/CD を使うと、 GitHub プラットフォームで何かイベントが発生するたびに一連のコマンドを実行することができます。よくある選択のひとつは、 push イベントを引き金にしてあるワークフローを行うというものです。このガイドでは、タグ付きのコミットが push されるたびに Python 配布物を公開するやり方をお見せします。それには pypa/gh-action-pypi-publish GitHub Action を使って出版 <publish> します。また、一時的な格納やソースコード配布物のダウンロードのためにGitHubの upload-artifactdownload-artifact を使います。

注意

このガイドでは、配布物をビルドするやり方を知っているプロジェクトが既にそんざいして、それが GitHub に置いてある ことを 前提 にしています。このガイドは、また、特定のプロジェクトをビルドするプラットフォームの詳細に立ち入ることはしません。もしあなたがバイナリのコンポーネントを持っているなら、 cibuildwheel にある GitHub Action の例を調べてみてください。

信頼された出版 <publish> を設定する#

このガイド文書は、GitHub Actions CI/CD に接続するために PyPI の 信頼ある出版 の実装に依存しています。これは、生成されるトークンが各プロジェクトでそれぞれ独立に作成されて自動的に期限切れになるというセキュリティ上の理由から推奨されています。さもなければ、 PyPI と TestPyPI の両方について API トークン を生成する必要があるでしょう。devpi のような第三者パーティのインデックス向けに出版 <publish> する場合には、ユーザ名とパスワードの組み合わせを提供する必要があるかもしれません。

このガイドではPyPIとTestPyPIの両方へのアップロードを実証しますので、ふたつの信頼あるパブリッシャが設定されていることが必要になるでしょう。以下に示すステップによって、新しい PyPI プロジェクト 用の " ペンディングされた" パブリッシャ群を作成する手順を一通りお見せします。しかしながら、あなたが所有者であるならば、任意の既存プロジェクトに 信頼ある出版 を追加することが可能でもあります。

注意

このガイドの以前のバージョンを読み通したことがあるのであれば、PyPI や TestPyPI への直接のアクセスをするために PYPI_API_TOKENTEST_PYPOI_API_TOKEN という秘密のトークンを作成したことでしょう。今ではこれらは過去のものとなっていて、旧来の設定を新しいものに置き換える際に GitHub リポジトリから削除し、 PyPI や TestPyPI のアカウント設定から取り除くべきです。

始めましょう! 🚀

  1. https://pypi.org/manage/account/publishing/ へ行く。

  2. 新しい PyPI プロジェクト 向けに発行したいと思う名前を (setup.cfgpyproject.toml の中の 名称 <name> の値として)、GitHub のリポジトリ所有者の名称 (org または user) と、リポジトリの名称と、 .github/ フォルダの下のリリースワークフローの名称を記入するには、 ワークフロー定義 をみてください。最後に、これからリポジトリ内に設定しようとするGutHub 環境 (pypi) の名称を追加してください。トラステッドパブリッシャを登録してください。

  3. ここで https://test.pypi.org/manage/account/publishing/ へ行って第2のステップを繰り返してください、ただし、今回は GitHub 環境の名称として testpypi を入力します。

  4. ペンディングされたパブリッシャは今やその初回使用の準備ができており、初回使用を行えば自動的にあなたのプロジェクトを生成します。

    注釈

    TestPyPI のアカウントを持っていなければ、新たに作成する必要があります。これは通常の PyPI のアカウントとは別のものです。

    注意

    セキュリティ上の理由から、pypi 環境での実行の度に、 手動での承認 を要求しなければなりません。

ワークフロー定義を作成する#

GitHub CI/CD ワークフローは、リポジトリの .github/workflows/ ディレクトリに置かれた YAML ファイルで宣言されます。

.github/workflows/publish-to-test-pypi.yml ファイルを作成しましょう。

意味のある名前で始めて、 GitHub がこのワークフローを走らせるべきイベントを定義しましょう:

name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI

on: push

プロジェクトをチェックアウトして配布物をビルドする#

PyPI と TestPyPI に向けて出版 <publish> するためには、二つのジョブを定義しなければならず、また、配布物のパッケージ群をビルドするための追加のジョブも定義しなければなりません。

最初に、あなたのプロジェクトの dist パッケージをビルドして、その後の使用のために保存するジョブを定義しましょう:

jobs:
  build:
    name: Build distribution 📦
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.x"

これによって、あなたのリポジトリを CI ランナーにダウンロードして、利用可能な最新の Python 3 リリースをインストールしアクティベートすることになります。

そして、今や我々はソースコードから dist 配布物をビルドして保存しておくことができます。この例では、パッケージを build することになります。ですから、これをステップリストに加えましょう:

    - name: Install pypa/build
      run: >-
        python3 -m
        pip install
        build
        --user
    - name: Build a binary wheel and a source tarball
      run: python3 -m build
    - name: Store the distribution packages
      uses: actions/upload-artifact@v3
      with:
        name: python-package-distributions
        path: dist/

ワークフローのジョブ環境を定義する#

さて、PyPI へ発行 <publish> される予定のジョブに初期設定を追加しましょう。それは、後ほど定義するであろうコマンドを実行するプロセスになるでしょう。このガイド文書では、

  publish-to-pypi:
    name: >-
      Publish Python 🐍 distribution 📦 to PyPI
    if: startsWith(github.ref, 'refs/tags/')  # only publish to PyPI on tag pushes
    needs:
    - build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/<package-name>  # Replace <package-name> with your PyPI project name
    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

これは、当該コミットがタグ付きである時にだけ PyPI 公開 <publishing> ワークフローが起動されることをも保証するものです。

PyPI へ配布物を公開する <publishng>#

最後に、次の手続きを末尾に追加しましょう:

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

この手順では、保存されている配布パッケージが download-artifact アクションによってダウンロードされた後に pypa/gh-action-pypi-publish という GitHub Action: を使って、 dist/ フォルダの内容物を無条件に PyPI へアップロードします。

配布パッケージに署名する#

以下に述べるジョブは、CPython に署名するのに使われる`_ ものと同じアーティファクトである Sigstore を使って配布パッケージに署名します。

第一に、配布物パッケージに署名するのに sigstore/gh-action-sigstore-python GitHub Action を使います。次の段階では、現在のタグから空の GitHub Release が``hg`` CLI を使って作成されます。この段階をさらにカスタマイズすることができる点に留意してください。リファレンスとしては、gh リリース説明文書 を見てください。

Tip

GitHub リリースの作成ができるような GITHUB_TOKEN パーミッションを維持する必要があります。やり方については、 GitHub 説明文書 を見てください。具体的に言えば、トークンには contents: write 権限が必要です。

最後に、署名された配布物が GitHub Release へアップロードされます。

  github-release:
    name: >-
      Sign the Python 🐍 distribution 📦 with Sigstore
      and upload them to GitHub Release
    needs:
    - publish-to-pypi
    runs-on: ubuntu-latest

    permissions:
      contents: write  # IMPORTANT: mandatory for making GitHub Releases
      id-token: write  # IMPORTANT: mandatory for sigstore

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Sign the dists with Sigstore
      uses: sigstore/gh-action-sigstore-python@v2.1.1
      with:
        inputs: >-
          ./dist/*.tar.gz
          ./dist/*.whl
    - name: Create GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      run: >-
        gh release create
        '${{ github.ref_name }}'
        --repo '${{ github.repository }}'
        --notes ""
    - name: Upload artifact signatures to GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      # Upload to GitHub Release using the `gh` CLI.
      # `dist/` contains the built packages, and the
      # sigstore-produced signatures and certificates.
      run: >-
        gh release upload
        '${{ github.ref_name }}' dist/**
        --repo '${{ github.repository }}'

注釈

これは、 PyPI から削除された GPG 署名を代替するものです。しかしながら、このジョブは PyPI へアップロードする時に必須のものと言うわけではなく、省略することもできます。

TestPyPI へ公開 <publish> するもうひとつのワークフロー#

さて、これらのステップを繰り返して、 jobs セクションに TestPyPI パッケージインデックスに公開 <publish> するようなもうひとつのジョブを作成しましょう:

  publish-to-testpypi:
    name: Publish Python 🐍 distribution 📦 to TestPyPI
    needs:
    - build
    runs-on: ubuntu-latest

    environment:
      name: testpypi
      url: https://test.pypi.org/p/<package-name>

    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to TestPyPI
      uses: pypa/gh-action-pypi-publish@release/v1
      with:
        repository-url: https://test.pypi.org/legacy/

Tip

testpypi Github 環境において手動の承認を要求することは、main ブランチへのコミット毎に走るように設計されていることから、典型的な場合には不必要とされていて、使われるとすればリリースの公開 <publish> パイプラインが健全であることを示すためであることが多いのです。

CI/CD ワークフローの全体像#

この段落には、上述のガイド文書に従った場合のワークフローの全体像を披露します。

Click here to display the entire GitHub Actions CI/CD workflow definition
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI

on: push

jobs:
  build:
    name: Build distribution 📦
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: "3.x"
    - name: Install pypa/build
      run: >-
        python3 -m
        pip install
        build
        --user
    - name: Build a binary wheel and a source tarball
      run: python3 -m build
    - name: Store the distribution packages
      uses: actions/upload-artifact@v3
      with:
        name: python-package-distributions
        path: dist/

  publish-to-pypi:
    name: >-
      Publish Python 🐍 distribution 📦 to PyPI
    if: startsWith(github.ref, 'refs/tags/')  # only publish to PyPI on tag pushes
    needs:
    - build
    runs-on: ubuntu-latest
    environment:
      name: pypi
      url: https://pypi.org/p/<package-name>  # Replace <package-name> with your PyPI project name
    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

  github-release:
    name: >-
      Sign the Python 🐍 distribution 📦 with Sigstore
      and upload them to GitHub Release
    needs:
    - publish-to-pypi
    runs-on: ubuntu-latest

    permissions:
      contents: write  # IMPORTANT: mandatory for making GitHub Releases
      id-token: write  # IMPORTANT: mandatory for sigstore

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Sign the dists with Sigstore
      uses: sigstore/gh-action-sigstore-python@v2.1.1
      with:
        inputs: >-
          ./dist/*.tar.gz
          ./dist/*.whl
    - name: Create GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      run: >-
        gh release create
        '${{ github.ref_name }}'
        --repo '${{ github.repository }}'
        --notes ""
    - name: Upload artifact signatures to GitHub Release
      env:
        GITHUB_TOKEN: ${{ github.token }}
      # Upload to GitHub Release using the `gh` CLI.
      # `dist/` contains the built packages, and the
      # sigstore-produced signatures and certificates.
      run: >-
        gh release upload
        '${{ github.ref_name }}' dist/**
        --repo '${{ github.repository }}'

  publish-to-testpypi:
    name: Publish Python 🐍 distribution 📦 to TestPyPI
    needs:
    - build
    runs-on: ubuntu-latest

    environment:
      name: testpypi
      url: https://test.pypi.org/p/<package-name>

    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v3
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to TestPyPI
      uses: pypa/gh-action-pypi-publish@release/v1
      with:
        repository-url: https://test.pypi.org/legacy/

これでできましたよ、皆さん!#

これで、手元の Git リポジトリをリモートの GitHub にタグ付きのコミットをプッシュする時はいつでも、このワークフローがそれを PyPI へ公開します。そして、プッシュしさえすればいつでも TestPyPI で公開されますので、アルファ版のユーザにテストビルドを提供するためにも、あなたのリリースパイプラインが健全な状態に保たれていることを確認するためにも役に立ちます!

注意

リポジトリで活発に頻繁なコミットがあって、前述のようにあらゆるプッシュが TestPyPI へアップロードされているなら、そのプロジェクトは PyPI プロジェクトサイズ制限 を超過するかもしれません。この制限を緩和することもできますが、より良い解決策は試験目的の CI の中で pypiserver のような PyPI と互換のあるサーバを使うように構成することかもしれません。

注釈

統合された GitHub Actions を最新版に保ち、頻繁にアップデートすることを推奨します。