
Windows向けwheelビルドエラーを直す実践ガイド:GitHub Actionsで安定して配布するための設計と落とし穴
WindowsでPythonのwheel(.whl)を作ろうとした途端にCIが赤くなる──そんな場面は珍しくありません。しかも、手元では通るのにGitHub ActionsのWindowsだけ失敗する、あるいは別OSのジョブが「優先度の高い待機があるのでキャンセル」となってログが追いにくい、という形で問題が見えづらくなりがちです。
この記事では、Windows wheelのビルドエラーを潰し込むための典型原因、再現と切り分け、GitHub Actionsの運用(キャンセル・同時実行制御)までを、実装者視点でまとめます。
- Windows向けwheelビルドエラーを直す実践ガイド:GitHub Actionsで安定して配布するための設計と落とし穴
まず押さえる:Windows wheelが壊れやすい理由
Windowsのwheelビルドは、Linux/macOSに比べて「前提が揃っていない」と失敗します。特にC/C++拡張を含むパッケージでは、次の要素が絡み合います。
-
MSVCツールチェーンの選択(Visual Studio Build Tools / clang-cl など)
-
CMake + Ninja/Visual Studioジェネレータの差
-
依存DLLの配置(同梱漏れ、PATH依存、遅延ロード)
-
Python ABIとタグの整合(cp310/cp311、win_amd64 など)
-
パス長制限・文字コード・改行(地味に刺さる)
「Windows wheelだけ落ちる」は、だいたい上のどれかが原因です。逆に言えば、チェックポイントを順番に潰すと収束します。
失敗ログが取れない問題:ジョブがキャンセルされるときの対処
CIで「優先度の高い待機があるのでキャンセル」という動きが出ると、肝心の失敗ログが揃わず、修正の手掛かりが消えます。これはGitHub Actionsの同時実行やキュー運用の影響で、特定ラベル・特定ワークフローが先に回されるケースで起きます。
対策1:concurrencyで意図した単位でまとめる
同じPR・同じブランチの実行を束ね、古い実行を自動キャンセルすることで「ログが分散して追えない」を減らせます。
-
グループ例:
workflow名 + PR番号(またはref) -
cancel-in-progress: trueを使うと最新だけ残る
対策2:失敗時に必ず成果物を残す
wheel生成に失敗しても、ビルドディレクトリやCMakeログ、pipのbuildログをアップロードしておくと切り分けが一気に進みます。最低限、次は成果物に含めたいところです。
-
CMakeCache.txt -
CMakeFiles/CMakeOutput.log,CMakeFiles/CMakeError.log -
pip -vのログ(可能ならファイル化) -
生成された
.pyd/.dllの一覧
切り分けの黄金手順:Windows wheelエラーを最短で潰す
ここからが本題です。失敗のタイプ別に、確認順を固定するとハマりません。
1) まず「どの段階で落ちているか」を3分類する
Windows wheelの失敗は、ほぼ次のどれかです。
-
ビルド環境構築で失敗(コンパイラがない、CMakeがない)
-
コンパイル/リンクで失敗(未解決外部参照、ライブラリ不足)
-
wheel作成後に失敗(DLL同梱漏れ、import時クラッシュ、タグ不整合)
ログの末尾だけ見ず、pip wheel や python -m build のどのフェーズかを最初に決めてください。対策が完全に変わります。
2) ツールチェーンを固定する(MSVCの揺れを消す)
GitHub ActionsのWindowsでは、Visual Studio関連が複数入っており、環境変数次第で拾うバージョンが揺れます。対策は「固定」です。
-
actions/setup-pythonでPythonを固定 -
C/C++は MSVC(cl.exe)を前提にするか、clang-clに統一する
-
CMakeジェネレータも Ninja に寄せる(Visual Studioジェネレータは差分が出やすい)
よくある失敗:
-
link.exeが見つからない -
MSB8020(v142/v143などツールセット不一致) -
/std:c++17指定が通らない(古いclを拾っている)
3) 依存ライブラリの探し方を「PATH依存」から脱却する
WindowsはDLL探索順が分かりづらく、CI上でたまたま通っても配布後に死にます。wheel配布を前提にするなら、次が鉄則です。
-
.pydが依存する.dllは wheel内に同梱する -
実行時にPATHへ依存しない(ユーザー環境の差で壊れる)
-
delvewheel(Windows向けの依存DLL同梱)を使う設計にする
典型的な症状:
-
ビルドは成功するが
importでDLL load failed -
ローカルでは通るがクリーン環境で失敗
-
OpenMPやCUDA、ONNX系のDLLが見つからない
4) 長いパス・文字コード・改行の地雷を避ける
Windowsには地味な罠が多いです。
-
チェックアウトパスが深いとCMakeが落ちる
-
生成物のパスが長くなり、ファイル操作が失敗
-
ソースにUTF-8以外が混ざるとコンパイラ警告が致命化することがある
対策として、CIでは
-
作業ディレクトリを浅くする(可能なら)
-
生成物の出力先を短いパスに固定
-
PYTHONUTF8=1やchcp 65001を検討(プロジェクト方針次第)
5) wheelタグとABIを機械的に確認する
「ビルドは通るのにインストールできない」はタグ問題が多いです。
-
生成wheelが
cp311-win_amd64なのか -
abi3を狙うなら設定が徹底されているか -
32bitが混ざっていないか(x86とx64の混在)
ここはログよりも、生成物のファイル名とpip debug -vの対応で機械的に潰せます。
実務で強い構成:cibuildwheelでWindows wheelを安定化
複数Python版・複数OSで配布するなら、ワークフローを自前で積むより cibuildwheel を軸にした方が安定します。理由は単純で、OSごとの作法(WindowsのMSVC、macOSのデプロイターゲット、Linuxのmanylinux)を「既に踏み抜いた設計」になっているからです。
ポイントは次の運用方針です。
-
Windowsだけ特別扱いせず、同じ入口(cibuildwheel)で回す
-
例外が必要なら環境変数で最小限に切る
-
失敗したときのために、ビルドログと生成物を必ずArtifacts化する
まとめ:直すべきは「エラー」より「揺れ」
Windows wheelのビルドエラーは、個別のコンパイルエラーを直すだけでは再発します。安定化の本質は、次の3点で“揺れ”を消すことです。
-
ツールチェーン(MSVC/CMake/ジェネレータ)を固定する
-
依存DLLをwheelに同梱し、実行時PATH依存をやめる
-
CI運用(concurrency・Artifacts)でログと再現性を確保する
この3つを先に整えると、個々のエラー修正が短期戦になります。結果的に、Windows向けwheel配布の信頼性が上がり、リリース作業が「祈る時間」から「確認する時間」に変わります。