cibuildwheel で python bwheel(C++ モジュール含む) を CI で一括ビルドし PyPI へアップロードするメモ  

漢なら Python pip で Windows, macOS, Linux で C++ モジュール含んだ prebuilt wheel インストールしたいですね!

cibuildwheel でやりましょう!

https://github.com/joerick/cibuildwheel

いろんな環境と python バージョンでビルドしてくれるスクリプトです. 素晴らしいですね.
これを自前プロジェクトの CI(travis, GitHub Actions, etc)に取り込めばいいです.
 
ちょっとわかりずらいですが, 

```
$ python3 -m cibuildwheel --output-dir wheelhouse
```

を実行することで, `python setup.py bdist_wheel` 相当を, それぞれの python バージョンの環境を構築して実行してくれます
(Linux, macOS の場合は Docker 利用).
 
## ビルド前にスクリプトを実行する

cibuildwheel では, Linux, macOS ですと Docker コンテナで実行されるため, Travis などで CI ビルドを行う場合は, `git submodule update` みたいなのは docker コンテナの内部で実行しないといけません.

cibuildwheel の環境変数で, いくつか設定することができます.

https://cibuildwheel.readthedocs.io/en/latest/options/

## ARM64 対応

https://qiita.com/methane/items/41c7d92d603552f2ef33

ありがとうございます.

Github Actions ですと, Linux での arm64 ビルドは qemu になり遅いです.
Cirrus CI を使うとよいでしょう.

https://zenn.dev/syoyo/articles/2ee2451bdc3d3f

Windows ですと, cross-compile での対応になります(test は走らせられない).
また, x86 のビルドと混在すると, x86 ビルドで残っている .obj キャッシュを参照するようになりビルド(リンク)がこけます.

ビルドごとに毎回ビルドキャッシュを clean するのがよいでしょうが, とりあえずは job を分けるのがよいでしょうか.
TinyUSDZ での例を参照ください.

https://github.com/syoyo/tinyusdz/blob/dev/.github/workflows/wheels.yml



## pypi にアップロードする

PyPI に Linux バイナリでパッケージをアップロードするメモ
https://qiita.com/syoyo/items/6185380b8d9950b25561

あたりを参照ください

### Github Actions では trusted publisher 推奨っぽい.

2023/12 月時点では, Github Actions で pypi にアップロードする場合, アクセストークン(Encrypted environment variable)ではなく, Trusted Publisher を使うのが推奨のようです.

https://docs.pypi.org/trusted-publishers/

要はアクセストークンだと無限期間有効でセキュリティの懸念があるので, 短期でのみ有効なトークン(15 分間だけ)を CI での upload 時に作成してセキュリティ高めましょうという感じです. 2023/12 時点では CI サービスは Github Actions のみの対応になっています.

### Azure pipeline は...?

Azure pipeline だと, twine などでビルド後アップロード自動化は現状めんどそうですが, vispy の設定が参考になるでしょうか.

https://github.com/vispy/vispy/blob/master/azure-pipelines.yml

一応 template が提案されていたりしますが, いくつか制約などがある感じです.

https://github.com/joerick/cibuildwheel/issues/229

実際 tinyobjloader で使ってみましたが, オンラインドキュメントがわかりづらく, Travis に比べて面倒な感じでした. 
(yaml 記述はオンラインエディタで構文チェックできるのがよいですが)

Travis が使えるなら travis を使う(or Github actions)を推奨します.

#### ビルド and publish の流れ

* OS ごとに cibuildwheel で whl ビルド
* `CopyFiles@2` で staging folder(ジョブが終わっても残っているディスク領域?)にビルド物をコピー
  * `$(Build.ArtifactStagingDirectory)` がステージングフォルダ 
* `PublishBuildArtifacsts@0` で, artifact のフォルダに名前をつける

```
      - task: PublishBuildArtifacts@1
        inputs:
          path: $(Build.ArtifactStagingDirectory)
          artifactName: tinyobjDeployWindows
```

デプロイ(e.g. PyPI へのアップロード)では, 
まず依存するタスクが完了しているか(`dependsOn`)と, tag コミットであることをチェックします.

```
  - job: deployPyPI
    # Based on vispy: https://github.com/vispy/vispy/blob/master/azure-pipelines.yml
    pool: {vmImage: 'Ubuntu-16.04'}
    condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
    dependsOn:
      - linux
      - macos
      - windows
```

その後, `DownloadBuildArtifacts` で, job のアーティファクト(実態としてはフォルダ群)をダウンロードします.

```
      - task: DownloadBuildArtifacts@0
        inputs:
          artifactName: 'tinyobjDeployWindows'
          downloadPath: $(Pipeline.Workspace)
```

このとき, `artifactName` でフォルダが作られ(`$(Pipeline.Workspace)/tinyobjDepoloyWinwdows`), その中にビルド物が配置されます.
デバッグでは, ls などでログに出力して確認できるようにしておくとよさそうです.

あとは twine で publish して完成です!

```
      # Publish to PyPI through twine
      - bash: |
          cd $(Pipeline.Workspace)
          find .
          python -m pip install --upgrade pip
          pip install twine
          echo tinyobjDeployLinux/python/dist/*
          echo tinyobjDeployLinux/python/wheelhouse/* tinyobjDeployMacOS/python/wheelhouse/* tinyobjDeployWindows/python/wheelhouse/*
          twine upload -u "__token__" --skip-existing tinyobjDeployLinux/python/dist/* tinyobjDeployLinux/python/wheelhouse/* tinyobjDeployMacOS/python/wheelhouse/* tinyobjDeployWindows/python/wheelhouse/*
        env:
          TWINE_PASSWORD: $(pypiToken2)
```

TWINE のパスワード(シークレットトークン)は, `pypiToken2` の名前で Azure Pipeline の website でシークレットトークンを variable で登録しておきます.

#### 感想

毎回 git push してビルドトリガーして 10 分くらい待ち, エラーが出ると修正 & 再度 tag つけて push がめんどいので, 手元で試せる環境がほしいところです.


#### Twine token

Azure Pipeline でも, Twine(PyPi)のシークレットトークンを扱える secret variables の機能がありますので, そちらを利用できます.

https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables

Secret variable は, ビルドスクリプト内では環境変数経由で指定します.

#### PublishBuildArtifacts を使いましょう
 
`PublishPipelineArtifact` は `One or more errors occurred` というエラーメッセージにもなっていないログだけ出して(エラー理由がまったく表示されない)てうまくうごきません.

https://stackoverflow.com/questions/58841733/how-to-debug-azure-devops-task-publishpipelineartifact-when-one-or-more-errors-o

`PublishBuildArtifacts` を使いましょう.

## C++ コードを含んだ実際のサンプル

cibuildwheel して, pypi アップロード, 

pytinyexr(PyEXR) では Travis を使っています.

https://github.com/syoyo/PyEXR

tinyobjloader では Azure Pipeline を使っています.

https://github.com/tinyobjloader/tinyobjloader/blob/master/azure-pipelines.yml

### Py2.7(or pypy) + Windows + pybind11


少なくとも pypy はビルドエラーになるので, pp27-win32, pp36-win32 あたりは `CIBW_SKIP` に含めてビルドしないようにしておきましょう
(pypy 使っている人いるのだろうか?)

また, python2.7 + C++11 も基本は非推奨です.

https://cibuildwheel.readthedocs.io/en/stable/cpp_standards/

py27 を使わなければならない強い理由がなければ, python 2.7 サポートはしないほうがいいでしょう.

## TODO

* [ ] GitHub Actions でのサンプルを作る