GitHub Actions を活用した Python プロジェクトの CI

この記事は CAMPHOR- Advent Calendar 2019 17日目の記事です.16日目の記事は @kmconner の「家の電気使用量を可視化する」でした.

この記事では,先日公開された CI/CD ツール GitHub Actions を Python のプロジェクトの CI に利用する方法を紹介します.主に GitHub Actions 上での以下の内容についてカバーします:

  • tox を使ったマルチプラットフォーム・バージョンでのテスト
  • C言語拡張モジュールを扱う際の注意点
  • バイナリを含む wheel のビルド

ここでは WSGI アプリケーションのためのラインプロファイラ wsgi_lineprof で利用している設定を紹介します.説明は不要なのでとにかく設定を知りたいという方は,直接こちらのリポジトリをチェックしてみてください.

GitHub Actions のメリット・デメリット

Python のライブラリを開発する際は,複数の Python のバージョンでテストを行うことが一般的で,tox などの複数のバージョンでのテストを容易にするツールが充実しています.現時点で公式にセキュリティアップデートが提供されている CPython のバージョンだけでも,2.7・3.5・3.6・3.7・3.8 と5つのバージョンがあります.

また,Python C/API や Cython などを使った C言語拡張 (バイナリ) を含むライブラリを開発する場合,複数のプラットフォームでのテストやパッケージングを行う必要もあります.実際に自分自身で全ての環境を整えるのはかなり面倒な作業です.

GitHub Actions には以下のような特徴があり,複数プラットフォーム・複数バージョンを用いた Python の CI を実行するのに適した環境が整っています:

GitHub Actions を利用するデメリットとしては,現時点ではまだ利用者が比較的少なくベストプラクティスが確立していない点や,Python 3.8 のリリースから GitHub Actions での対応までの時間がかかるといった公式のサポートに若干の不安を覚える点が挙げられます.

複数の環境でのテストの実行

まずは,GitHub Actions を使って複数のプラットフォーム・Python バージョンでテストを実行する方法を紹介します.

tox の設定

複数の Python バージョンでのテストを実現するために,テストツールに tox を導入します.CI ツールの機能を使って複数バージョンのテストを実現しても良いのですが,この場合は他の CI ツールへの移行が面倒になったり,ローカルでテストする際の方法を別に用意する必要生じたりするため,tox を利用することをおすすめします.

Python 2.7,3.5〜3.8 上で pytest を使ってテストを実行するための設定 (tox.ini) は以下のようになります:

[tox]
envlist = py27,py35,py36,py37,py38

[gh-actions]
python =
    2.7: py27
    3.5: py35
    3.6: py36
    3.7: py37
    3.8: py38

[testenv]
deps = pytest
commands =
    pytest

tox-gh-actions

[gh-actions] のセクションは tox-gh-actions のための設定です.tox-gh-actions を導入すると,GitHub Actions のジョブでインストールされている Python のバージョンのテストのみを実行することが出来ます.Travis CI のための tox-travis の GitHub Actions 版だと思ってもらって構いません.

GitHub Actions の設定

次に GitHub Actions を設定します.以下の設定 (.github/workflows/tests.yml) は GitHub にコードが push された際に tox を使ってテストを実行します.

name: Tests

on: [push]

jobs:
  build:
    runs-on: ${{ matrix.platform }}
    strategy:
      max-parallel: 15
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-latest]
        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip setuptools wheel
        python -m pip install tox tox-gh-actions
    - name: Test with tox
      run: tox

jobs.build.strategy.matrix を利用することで3つのOSと5つのPythonバージョンを組み合わせた15通りのジョブが実行されます.それぞれのジョブの中で最新の pip・setuptools・wheel をインストールしたのち,tox・tox-gh-actions をインストールして,テストを実行しています.

C言語拡張モジュールの使用

C言語拡張をビルドする必要がある場合 (依存ライブラリが利用する拡張をビルドしないといけない場合や,Python/C API・Cython を使ったライブラリを開発している場合など),上記の設定だけではビルドが失敗する可能性があります.この場合各プラットフォームで追加のパッケージの導入や設定を行う必要があります.全ての例を網羅的に説明することは難しいので,ここではいくつかの例を紹介します.

Python 2.7 + Windows

Python 2.7 で Windows 向けのバイナリをビルドする場合,比較的古いバージョンの Microsoft Visual C++ Compiler が必要になります.(公式が配布している CPython のビルドに使われているコンパイラのバージョンの情報はこちら.) Microsoft が必要なコンパイラを配信してくれているので,以下のような設定を追加して GitHub Actions のジョブでコンパイラをインストールします.

jobs:
  build:
    runs-on: ${{ matrix.platform }}
    strategy:
      max-parallel: 6
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-2019]
        python-version: [2.7, 3.5, 3.6, 3.7, 3.8]

    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    # Install Microsoft Visual C++ Compiler for Python 2.7
    # http://aka.ms/vcpython27
    - name: Install MSVC++ for Python 2.7
      if: startsWith(matrix.platform, 'windows-') && matrix.python-version == 2.7
      run: choco install vcpython27 -y

    (以下省略)

Cannot open include file: ‘io.h’: No such file or directory

Windows 環境で上記のようなエラーが発生した場合は以下のように INCLUDE にパスを追加することで解決することが出来ました.

    - name: Test with tox
      run: tox
      env:
        # This is for fixing the error like this:
        # pyconfig.h(68): fatal error C1083: Cannot open include file: 'io.h': No such file or directory
        INCLUDE: c:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/SDK/ScopeCppSDK/SDK/include/ucrt

バイナリを含む wheel のビルド

Python の wheel とはパッケージのフォーマットの一種で,コンパイル済みのバイナリ (C言語拡張) を含められることが特徴です.これにより,パッケージを使用するユーザーはインストール時に拡張モジュールをビルドする必要がなく簡単に利用できます.著名なライブラリでは numpy が各プラットフォーム向けの wheel を配布しています.

しかし,wheel はプラットフォーム (OS) と Python の組み合わせごとに作成する必要があるため,個人レベルで多くのプラットフォームのための wheel を作成することは面倒でした.GitHub Actions を用いると誰でも比較的簡単に多くのプラットフォームのための wheel を用意することが出来ます.

wheel をビルドするための設定は長くなってしまうため,この節では要点に絞って紹介します.完全な設定についてはこちらを参考にしてください.

Linux

Linux はディストリビューションによってライブラリのバージョンの組み合わせ等が異なるため,wheel をビルドするのは従来困難でした.最近では manylinux1/manylinux2010 といった仮想のプラットフォームの制限を満たす wheel をビルドすることで,Linux でも wheel の恩恵を享受できるようになっています.

manylinux1/manylinux2010 の wheel をビルドする場合は,まず GitHub Actions 上で Linux のジョブを実行し,その上で manylinux1/manylinux2010 の Docker イメージを使って wheel をビルドするのが良いでしょう.

macOS

macOS のための wheel は GitHub Actions のジョブ上で python setup.py bdist_wheel を実行して直接ビルドできます.デフォルトでは macosx_10_13_x86_64 のようなプラットフォームタグがつくため,macOS 10.13 High Sierra 以降でインストールできる wheel になります.より古い macOS で利用できるようにするには delocate のようなツールを使ってプラットフォームタグを変更する必要があります.

Windows

Windows のための wheel のビルドも,テストができる環境まで整っていれば比較的簡単です.ジョブ中で python setup.py bdist_wheel を実行すれば wheel を作成出来ます.32bit (x86) と 64bit (x64) の両方に対応するためにはそれぞれ wheel をビルドする必要があります.GitHub Actions で Python をインストールする際にアーキテクチャが指定できるため,それを利用するのが良いでしょう.

jobs:
  build_wheel_windows:
    runs-on: windows-2019
    strategy:
      max-parallel: 4
      matrix:
        python-version: [2.7, 3.6, 3.7, 3.8]
        architecture: ['x86', 'x64']
    steps:
    - uses: actions/checkout@v1
    - name: Set up Python ${{ matrix.python-version }} (${{ matrix.architecture }})
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
        architecture: ${{ matrix.architecture }}

wheel をアーティファクトとしてまとめる

公式のドキュメントでは説明されていないようですが,一つのワークフロー中で同じ名前を使って actions/upload-artifact を複数回実行でき,最終的に生成されるアーティファクトには全てのアップロード内容が含まれるようです.wheel のビルドの場合,それぞれのジョブで生成される wheel ファイルの名前は相異なるのでこの方法で簡単に一つのアーティファクトに全ての wheel をまとめることが出来ます.

まとめ

この記事では GitHub Actions を使って,Python プロジェクトをマルチプラットフォーム・バージョンでテストする方法や,C言語拡張をビルドする際の注意点,バイナリを wheel をビルドする方法などについて紹介しました.

今回紹介した内容を全て実装した場合,設定はそれなりに複雑なものになってしまいますが,GitHub Actions という一つのツールだけでこれらが実現できるようになったことは大きな進歩だと思います.現時点で他の CI ツールを使用している場合も,GitHub Actions を併用して試してみても良いかもしれません.

また,pypa/gh-action-pypi-publish のようなパッケージを PyPI にアップロードするアクションも開発されているので,これらも組み合わせることでライブラリのリリースプロセスをさらに簡単にすることができるかもしれません.

CAMPHOR- Advent Calendar 2019 18日目の担当は @yckao です.お楽しみに!