diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c5dda3..5c1c62f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,13 @@ uv run python scripts/run_actionlint.py when it is not cached. A passing static workflow check does not mean the workflow has run successfully on GitHub. +Both Hatch build targets explicitly emit Core Metadata 2.4, which the locked +Twine toolchain can validate. Build isolation resolves Hatchling separately from +`uv.lock`, so relying on its default metadata version can break an unchanged +checkout. The build regression test checks both the sdist-to-wheel path and a +direct wheel build with the locked Twine validator. Change the metadata format +only together with verified support in the validation and publication tools. + If a check is unavailable, say so in the pull request rather than describing it as passing. diff --git a/pyproject.toml b/pyproject.toml index 49568cf..4359dff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ requires = ["hatchling>=1.27.0,<2.0.0"] build-backend = "hatchling.build" [tool.hatch.build.targets.sdist] +core-metadata-version = "2.4" include = [ "/AGENTS.md", "/ARCHITECTURE.md", @@ -71,6 +72,7 @@ include = [ ] [tool.hatch.build.targets.wheel] +core-metadata-version = "2.4" packages = ["src/cometapi"] [tool.pyright] diff --git a/tests/test_release_documents.py b/tests/test_release_documents.py index 590dc0b..b5d54ae 100644 --- a/tests/test_release_documents.py +++ b/tests/test_release_documents.py @@ -1576,6 +1576,30 @@ def test_artifact_metadata_accepts_publication_neutral_long_description() -> Non ) +@pytest.mark.parametrize("build_options", [[], ["--wheel"]], ids=["sdist-to-wheel", "direct-wheel"]) +def test_built_distributions_pass_locked_twine_check( + tmp_path: Path, build_options: list[str] +) -> None: + """Exercise the isolated build backend against the locked metadata validator.""" + built = tmp_path / "distributions" + subprocess.run( + ["uv", "build", *build_options, "--out-dir", str(built)], + cwd=PROJECT_ROOT, + check=True, + capture_output=True, + text=True, + ) + artifacts = sorted(built.glob("*.whl")) + sorted(built.glob("*.tar.gz")) + assert len(artifacts) == (1 if build_options else 2) + subprocess.run( + [sys.executable, "-m", "twine", "check", "--strict", *map(str, artifacts)], + cwd=tmp_path, + check=True, + capture_output=True, + text=True, + ) + + @pytest.mark.parametrize( "description", [