Error [setuptools-scm was unable to detect version]

setuptools-scm was unable to detect version の直し方(.git の無い環境で pip がビルドできないとき)

FIX SUMMARY verified
Applies when
python:3.12-slimsetuptools_scm 8 (dynamic version from SCM)building a setuptools_scm package where .git is absent (GitHub ZIP / Docker COPY excluding .git via .dockerignore / git archive / vendored source)

Verified: reproduced in python:3.12-slim, then the setuptools-scm was unable to detect version signature was gone after the fix (exit 0).

pip install(自作パッケージや、setuptools_scm を使うライブラリをソースから入れるとき)の途中で、ビルドに入り、次のエラーで止まることがあります。

LookupError: setuptools-scm was unable to detect version for /path/to/pkg.

続けて、setuptools_scm 自身がヒントを出します。

Make sure you're either building from a fully intact git repository or PyPI tarballs. Most other sources (such as GitHub's tarballs, a git checkout without the .git folder) don't contain the necessary metadata and will not work.

このエラーの上には、pip の傘のメッセージ(error: subprocess-exited-with-errorerror: metadata-generation-failed)が出ます。ただしこの傘の行は pip の版や落ちた段で変わり、それ自体は原因を教えてくれません。原因を名指ししているのは、その内側の setuptools-scm was unable to detect version の行です。まずここを読んでください。

これは、そのパッケージが バージョン番号を git(タグや履歴)から自動で決める設定になっていて、ビルドしている場所に .git が無いために、版を決められずに止まったものです。

なぜ git を見に行くのか

setuptools_scm は、pyproject.toml に版番号を直書きする代わりに、SCM(git など)のタグや履歴から版を導出するツールです。[project]versiondynamic にし、[tool.setuptools_scm] を置くと有効になります。

[build-system]
requires = ["setuptools>=61", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "demo-scm"
dynamic = ["version"]

[tool.setuptools_scm]

pip がこのパッケージをビルドするとき、メタデータを作る段で setuptools_scm が呼ばれ、版を計算しようとします。その材料が git のタグや履歴です。だから .git ディレクトリが見当たらないと、材料が無く、unable to detect version で止まります。PyPI からダウンロードした sdist(ソース配布物)には版が静的な情報として同梱されているので、そちらは git が無くても通ります。

なぜ .git が無いと出るのか

手元では、リポジトリを git clone した中で作業しているので、.git があり版が決まります。エラーになるのは、ソースは持っているのに .git がそこに無いときです。次のような場面で起きます。

  • GitHub の「Download ZIP」や、codeload の tarball からビルドしている:これらは .git を含みません。ヒント文が名指しするとおりです。
  • Docker の COPY . ..git が入っていない.dockerignore.git を除外していると、ビルドコンテキストに履歴が届かず、コンテナ内のビルドだけで出ます。手元の pip install . は通るのに CI・イメージビルドで落ちる、の典型です(.gitignore は Docker のビルドコンテキストには関係しません。参照されるのは .dockerignore です)。
  • git archive で作った tarball からビルドしているgit archive は既定で .git を含まないアーカイブを作るので、版の材料が残りません。
  • sdist を展開し直した、コピーしただけのソース.git を伴わないコピーは同じ症状になります。

いずれも「手元では出ないのに、別の環境でだけ出る」形で、.git がビルド地点に届いているかどうかが分かれ目です。

直し方1:版を明示する(.git を用意できない CI・コンテナ向け)

.git を持ち込めない環境では、版を環境変数で直接与えるのが応急手当になります。setuptools_scm が推奨するのは、対象パッケージ名を含む形です。

SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DEMO_SCM=0.1.0 pip install .

DEMO_SCM の部分は、パッケージ名(demo-scm)を PEP 503 に従って正規化した形です。大文字にし、.-_ の連続を単一の _ にまとめます(demo-scmDEMO_SCMmy.pkg-nameMY_PKG_NAME)。名前ごとに版を指定できるので、複数の setuptools_scm パッケージを同時にビルドしても取り違えません。パッケージを問わず一律に適用したいときは、名前を含まない SETUPTOOLS_SCM_PRETEND_VERSION も使えますが、そのビルドで走るすべての setuptools_scm パッケージに同じ版を与える点に注意してください。

Docker なら、ビルド段でこの環境変数を渡します。

ENV SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DEMO_SCM=0.1.0
RUN pip install .

これで版が確定し、git は要りません。ただし与える版は自分で管理することになります(タグと自動で同期はしません)。

直し方2:.git を届ける/配布物から入れる(恒久)

版を git 由来に保ったままにするなら、ビルドする場所に .git を届けるのが本筋です。

  • Docker では .git を除外しない.dockerignore から .git を外すか、.git を含む段でビルドします。マルチステージなら、SCM 情報を持つ段で pip install .(または wheel を作成)して成果物だけ後段へ渡す形にできます。
  • 第三者のパッケージを GitHub の ZIP から入れている場合:ZIP ではなく、PyPI のリリースpip install <パッケージ名>)か、git+ URL(pip install "git+https://github.com/user/[email protected]")から入れます。PyPI の sdist には版が同梱され、git+ は履歴ごと取得するので、どちらも setuptools_scm が版を決められます。
  • 自作パッケージを配る側なら、タグを打ってから sdist/wheel をビルドし、それを配布します。利用者はビルドし直さないので、この問題を踏みません。

切り分け(うまくいかないとき)

  • 傘のメッセージ(subprocess-exited-with-errormetadata-generation-failed)しか見ていない:その内側までスクロールして setuptools-scm was unable to detect version を探してください。傘の文言は pip の版や落ちる段で変わることがありますが、内側の原因行を読めば切り分けられます。
  • 手元では通るのに CI・Docker だけで落ちる:まず .git がビルド地点に届いているかを疑います。.dockerignore.git 除外、Download ZIP、git archive.git を伴わないコピーのいずれかであることがほとんどです。
  • SETUPTOOLS_SCM_PRETEND_VERSION_FOR_... を設定したのに、まだ同じエラーが出る:接尾辞の綴りを確認します。パッケージ名を PEP 503 で正規化した形(大文字化し、.-_ の連続を単一の _ に)と合っているかを見てください(名前を含まない SETUPTOOLS_SCM_PRETEND_VERSION で切り分けるのも手です)。
  • .git はあるのに版が意図しない値になる:これは版を導出できない本エラーとは別で、リリースタグを打っていないケースです。既定ではタグからの距離を含む開発版(0.1.dev5+g<hash> のような形)になります。素の 0.0.0fallback_version を設定しているときの値です。タグを打つか、明示的に版を与えてください。
  • shallow clone や worktree で版がずれる:これらは .git を保持するので本エラーは出ませんが、タグが履歴に見えないと版がずれることがあります。GitHub Actions の actions/checkout は既定が浅い取得(fetch-depth: 1)なので、タグが要るなら fetch-depth: 0 を指定します。
  • 内側が error: command 'gcc' failedcan't find Rust compiler になっている:これは版検出でなく、ソースビルドに要るコンパイラやビルド道具が無い別問題です。対処は error: command ‘gcc’ failedcan’t find Rust compiler を参照してください。

検証環境

  • python:3.12-slim、ネットワーク有り
  • 再現:setuptools_scm を使う最小パッケージ(上の pyproject.toml)を、.git の無いディレクトリで pip install すると、setuptools-scm was unable to detect version を出して終了コード 1
  • 修正:SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DEMO_SCM=0.1 を与えて同じ pip install を実行すると、終了コード 0

再現から修正までは errfix の検証ハーネスが機械的に確認しています。ここでは書き込み可能なディレクトリへソースを写してからビルドし(実環境の pip install . と同じ条件にするため)、.git が無いと版を決められないこと、版を環境変数で与えると同じインストールが通ることを、再現から消滅まで通しました。エラー文言とヒント文は実機出力から引用しています。Docker の COPY や GitHub の ZIP、環境変数名の正規化規則については、機械検証の対象は上記の1点に限り、一般的な引き金として setuptools_scm のドキュメント(版の上書きと正規化)に沿って書いています。

検証(machine-verified)

この修正は python:3.12-slim のバージョン固定コンテナ内で再現し、修正後に setuptools-scm was unable to detect version のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs python/setuptools-scm-no-version
● reproduce setuptools-scm was unable to detect version present ✓
● apply fix exit 0
● re-run setuptools-scm was unable to detect version gone ✓
PASS verified · python:3.12-slim · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。