diff --git a/.github/workflows/publish_tombstone.yml b/.github/workflows/publish_tombstone.yml index d078e25..6020a0c 100644 --- a/.github/workflows/publish_tombstone.yml +++ b/.github/workflows/publish_tombstone.yml @@ -54,7 +54,10 @@ jobs: - name: Build the tombstone sdists run: | - python3 -m pip install --upgrade build twine==6.0.1 + # setuptools is explicit: the sdist builds with --no-isolation, and + # Python 3.12+ runners no longer ship setuptools, so the backend + # would otherwise be missing (BackendUnavailable, run 32309423898). + python3 -m pip install --upgrade build twine==6.0.1 setuptools python3 -m autohands.tombstone --out dist-tombstone - name: Check metadata diff --git a/autohands/tombstone.py b/autohands/tombstone.py index f0c6a82..3fcd128 100644 --- a/autohands/tombstone.py +++ b/autohands/tombstone.py @@ -67,6 +67,7 @@ from __future__ import annotations import argparse +import importlib.util import shutil import subprocess import sys @@ -251,6 +252,27 @@ def write_project( return project_dir +def require_setuptools() -> None: + """Fail early, and legibly, when the build backend is missing. + + The sdist is built with `--no-isolation`, so setuptools has to be present in + this interpreter — and Python 3.12 dropped it from the default environment. + Without this check the failure surfaces as `BackendUnavailable: Cannot + import 'setuptools.build_meta'` from inside pyproject_hooks, which names + neither the cause nor the fix. A developer machine that happens to have + setuptools installed will not reproduce it, so the check has to be explicit + rather than left to whatever the environment happens to carry. + """ + if importlib.util.find_spec("setuptools") is None: + raise RuntimeError( + "setuptools is not installed in this interpreter, so the sdist cannot " + "be built. The build deliberately runs with --no-isolation so it " + "cannot silently reach the network for a backend; Python 3.12 and " + "later no longer ship setuptools by default. Install it first: " + "python3 -m pip install setuptools" + ) + + def build_sdist( project_dir: Path, out_dir: Path, @@ -268,6 +290,7 @@ def build_sdist( so "the newest tarball here" would happily hand back a sibling package's sdist and verify *its* metadata instead. """ + require_setuptools() out_dir.mkdir(parents=True, exist_ok=True) subprocess.run( [ diff --git a/tests/test_tombstone.py b/tests/test_tombstone.py index 882fd1b..7ea36ba 100644 --- a/tests/test_tombstone.py +++ b/tests/test_tombstone.py @@ -163,6 +163,19 @@ def _build_available(): ) +def test_missing_setuptools_is_reported_before_the_build(tmp_path, monkeypatch): + """`--no-isolation` means the backend must already be installed, and Python + 3.12 dropped setuptools from the default environment. Left unchecked the + failure arrives as `BackendUnavailable: Cannot import + 'setuptools.build_meta'` from inside pyproject_hooks, naming neither the + cause nor the fix — and a machine that happens to have setuptools will + never reproduce it (PyAutoHands run 32309423898).""" + monkeypatch.setattr(tombstone.importlib.util, "find_spec", lambda name: None) + + with pytest.raises(RuntimeError, match="pip install setuptools"): + tombstone.build_sdist(tmp_path / "project", tmp_path / "dist", "autolens") + + @requires_build def test_built_sdist_declares_sub_floor_requires_python(tmp_path): project = tombstone.write_project("autolens", tmp_path)