From 4e4e75dbf7e0ba8fbfae0d84cfffcd482dab2406 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 21:16:00 +0000 Subject: [PATCH 1/3] Initial plan From bb718b3ebc4f999a8cb467205b7b1f9b251bbfbe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 21:27:32 +0000 Subject: [PATCH 2/3] refactor macros to derive runtime test deps from setup.cfg Agent-Logs-Url: https://github.com/envoyproxy/toolshed/sessions/2c4bea7b-f6c0-4322-8f6e-b4f7e4d7190b Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- py/_test_publish_pkg/_test_publish_pkg/BUILD | 5 ---- py/aio.api.bazel/aio/api/bazel/BUILD | 5 ---- py/aio.api.bazel/tests/BUILD | 9 +----- py/aio.api.github/aio/api/github/BUILD | 7 ----- py/aio.api.github/tests/BUILD | 5 ---- py/aio.api.nist/aio/api/nist/BUILD | 6 ---- py/aio.api.nist/tests/BUILD | 7 +---- py/aio.core/tests/BUILD | 9 +----- py/aio.run.checker/aio/run/checker/BUILD | 3 -- py/aio.run.checker/tests/BUILD | 7 +---- py/aio.run.runner/aio/run/runner/BUILD | 8 ------ py/aio.run.runner/tests/BUILD | 8 +----- py/dependatool/dependatool/BUILD | 5 ---- py/envoy.base.utils/envoy/base/utils/BUILD | 17 ----------- py/envoy.base.utils/tests/BUILD | 6 ---- py/envoy.ci.report/envoy/ci/report/BUILD | 6 ---- py/envoy.ci.report/tests/BUILD | 8 +----- py/envoy.code.check/envoy/code/check/BUILD | 8 ------ py/envoy.code.check/tests/BUILD | 7 ----- .../envoy/dependency/check/BUILD | 11 -------- py/envoy.dependency.check/tests/BUILD | 13 +-------- .../envoy/distribution/release/BUILD | 5 ---- py/envoy.distribution.release/tests/BUILD | 1 - .../envoy/distribution/verify/BUILD | 2 -- py/envoy.distribution.verify/tests/BUILD | 2 -- .../envoy/docker/utils/BUILD | 3 -- .../envoy/docs/sphinx_runner/BUILD | 18 ------------ py/envoy.docs.sphinx_runner/tests/BUILD | 14 +--------- .../envoy/github/release/BUILD | 10 ------- py/envoy.github.release/tests/BUILD | 2 -- py/envoy.gpg.sign/envoy/gpg/sign/BUILD | 3 -- py/envoy.gpg.sign/tests/BUILD | 2 -- py/mypy-abstracts/mypy_abstracts/BUILD | 3 -- py/mypy-abstracts/tests/BUILD | 7 +---- py/pants-toolshed/macros.py | 28 +++++++++++++++++++ py/pytest-abstracts/pytest_abstracts/BUILD | 3 -- py/pytest-abstracts/tests/BUILD | 7 +---- 37 files changed, 38 insertions(+), 232 deletions(-) diff --git a/py/_test_publish_pkg/_test_publish_pkg/BUILD b/py/_test_publish_pkg/_test_publish_pkg/BUILD index b333edfc96..9f2f4dc091 100644 --- a/py/_test_publish_pkg/_test_publish_pkg/BUILD +++ b/py/_test_publish_pkg/_test_publish_pkg/BUILD @@ -8,11 +8,6 @@ target( name="dev_deps", - dependencies=[ - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#packaging", - "//py/deps:reqs#pyyaml", - ], ) toolshed_library( diff --git a/py/aio.api.bazel/aio/api/bazel/BUILD b/py/aio.api.bazel/aio/api/bazel/BUILD index 6771c2535c..2137231acc 100644 --- a/py/aio.api.bazel/aio/api/bazel/BUILD +++ b/py/aio.api.bazel/aio/api/bazel/BUILD @@ -1,11 +1,6 @@ toolshed_library( "aio.api.bazel", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - ], sources=[ "abstract/__init__.py", "abstract/base.py", diff --git a/py/aio.api.bazel/tests/BUILD b/py/aio.api.bazel/tests/BUILD index 35d1a38a58..d94bb91505 100644 --- a/py/aio.api.bazel/tests/BUILD +++ b/py/aio.api.bazel/tests/BUILD @@ -1,9 +1,2 @@ -toolshed_tests( - "aio.api.bazel", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - ], -) +toolshed_tests("aio.api.bazel") diff --git a/py/aio.api.github/aio/api/github/BUILD b/py/aio.api.github/aio/api/github/BUILD index 07ca0a5579..f47572a7bb 100644 --- a/py/aio.api.github/aio/api/github/BUILD +++ b/py/aio.api.github/aio/api/github/BUILD @@ -1,13 +1,6 @@ toolshed_library( "aio.api.github", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#gidgethub", - "//py/deps:reqs#packaging", - ], sources=[ "abstract/__init__.py", "abstract/actions/__init__.py", diff --git a/py/aio.api.github/tests/BUILD b/py/aio.api.github/tests/BUILD index 6e6421d47c..7b8825ede1 100644 --- a/py/aio.api.github/tests/BUILD +++ b/py/aio.api.github/tests/BUILD @@ -2,11 +2,6 @@ toolshed_tests( "aio.api.github", dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#gidgethub", - "//py/deps:reqs#packaging", "//py/deps:reqs#pytest-asyncio", ], ) diff --git a/py/aio.api.nist/aio/api/nist/BUILD b/py/aio.api.nist/aio/api/nist/BUILD index 7001e55277..1ac2bebb2e 100644 --- a/py/aio.api.nist/aio/api/nist/BUILD +++ b/py/aio.api.nist/aio/api/nist/BUILD @@ -1,12 +1,6 @@ toolshed_library( "aio.api.nist", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#packaging", - ], sources=[ "abstract/__init__.py", "abstract/cpe.py", diff --git a/py/aio.api.nist/tests/BUILD b/py/aio.api.nist/tests/BUILD index 7f893a17fc..20d991ebff 100644 --- a/py/aio.api.nist/tests/BUILD +++ b/py/aio.api.nist/tests/BUILD @@ -1,7 +1,2 @@ -toolshed_tests( - "aio.api.nist", - dependencies=[ - "//py/deps:reqs#abstracts", - ], -) +toolshed_tests("aio.api.nist") diff --git a/py/aio.core/tests/BUILD b/py/aio.core/tests/BUILD index 53bdeae757..bed5337581 100644 --- a/py/aio.core/tests/BUILD +++ b/py/aio.core/tests/BUILD @@ -1,9 +1,2 @@ -toolshed_tests( - "aio.core", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#pyyaml", - "//py/deps:reqs#trycast", - ], -) +toolshed_tests("aio.core") diff --git a/py/aio.run.checker/aio/run/checker/BUILD b/py/aio.run.checker/aio/run/checker/BUILD index 59fc9272ad..1a2fe4d636 100644 --- a/py/aio.run.checker/aio/run/checker/BUILD +++ b/py/aio.run.checker/aio/run/checker/BUILD @@ -1,7 +1,4 @@ toolshed_library( "aio.run.checker", - dependencies=[ - "//py/deps:reqs#aio-run-runner", - ], ) diff --git a/py/aio.run.checker/tests/BUILD b/py/aio.run.checker/tests/BUILD index 4b1f076eb6..4d35d0fdae 100644 --- a/py/aio.run.checker/tests/BUILD +++ b/py/aio.run.checker/tests/BUILD @@ -1,7 +1,2 @@ -toolshed_tests( - "aio.run.checker", - dependencies=[ - "//py/deps:reqs#aio-run-runner", - ], -) +toolshed_tests("aio.run.checker") diff --git a/py/aio.run.runner/aio/run/runner/BUILD b/py/aio.run.runner/aio/run/runner/BUILD index 6e3f09cf4c..d8216ff04f 100644 --- a/py/aio.run.runner/aio/run/runner/BUILD +++ b/py/aio.run.runner/aio/run/runner/BUILD @@ -1,12 +1,4 @@ toolshed_library( "aio.run.runner", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#coloredlogs", - "//py/deps:reqs#frozendict", - "//py/deps:reqs#uvloop", - "//py/deps:reqs#verboselogs", - ], ) diff --git a/py/aio.run.runner/tests/BUILD b/py/aio.run.runner/tests/BUILD index 31ac57a44b..317e544033 100644 --- a/py/aio.run.runner/tests/BUILD +++ b/py/aio.run.runner/tests/BUILD @@ -1,8 +1,2 @@ -toolshed_tests( - "aio.run.runner", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - ], -) +toolshed_tests("aio.run.runner") diff --git a/py/dependatool/dependatool/BUILD b/py/dependatool/dependatool/BUILD index 5bea831d0e..174b650874 100644 --- a/py/dependatool/dependatool/BUILD +++ b/py/dependatool/dependatool/BUILD @@ -1,11 +1,6 @@ toolshed_library( "dependatool", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-checker", - ], sources=[ "__init__.py", "abstract/__init__.py", diff --git a/py/envoy.base.utils/envoy/base/utils/BUILD b/py/envoy.base.utils/envoy/base/utils/BUILD index c7b69d7f0d..74a8b047ff 100644 --- a/py/envoy.base.utils/envoy/base/utils/BUILD +++ b/py/envoy.base.utils/envoy/base/utils/BUILD @@ -1,23 +1,6 @@ toolshed_library( "envoy.base.utils", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-api-github", - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#frozendict", - "//py/deps:reqs#jinja2", - "//py/deps:reqs#orjson", - "//py/deps:reqs#packaging", - "//py/deps:reqs#protobuf", - "//py/deps:reqs#python-gnupg", - "//py/deps:reqs#pyyaml", - "//py/deps:reqs#trycast", - "//py/deps:reqs#types-protobuf", - "//py/deps:reqs#zstandard", - ], sources=[ "__init__.py", "abstract/__init__.py", diff --git a/py/envoy.base.utils/tests/BUILD b/py/envoy.base.utils/tests/BUILD index 060126a2ab..17106add6b 100644 --- a/py/envoy.base.utils/tests/BUILD +++ b/py/envoy.base.utils/tests/BUILD @@ -3,12 +3,6 @@ toolshed_tests( "envoy.base.utils", dependencies=[ ":data", - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-api-github", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#pyyaml", - "//py/deps:reqs#trycast", ], ) diff --git a/py/envoy.ci.report/envoy/ci/report/BUILD b/py/envoy.ci.report/envoy/ci/report/BUILD index 0d44a1a5a9..5303a7e379 100644 --- a/py/envoy.ci.report/envoy/ci/report/BUILD +++ b/py/envoy.ci.report/envoy/ci/report/BUILD @@ -1,12 +1,6 @@ toolshed_library( "envoy.ci.report", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-api-github", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - ], sources=[ "__init__.py", "ci.py", diff --git a/py/envoy.ci.report/tests/BUILD b/py/envoy.ci.report/tests/BUILD index 620c7cc77c..8fafbda957 100644 --- a/py/envoy.ci.report/tests/BUILD +++ b/py/envoy.ci.report/tests/BUILD @@ -1,8 +1,2 @@ -toolshed_tests( - "envoy.ci.report", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - ], -) +toolshed_tests("envoy.ci.report") diff --git a/py/envoy.code.check/envoy/code/check/BUILD b/py/envoy.code.check/envoy/code/check/BUILD index e29e883330..9836a62335 100644 --- a/py/envoy.code.check/envoy/code/check/BUILD +++ b/py/envoy.code.check/envoy/code/check/BUILD @@ -2,15 +2,7 @@ toolshed_library( "envoy.code.check", dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-checker", "//py/envoy.base.utils/envoy/base/utils", - "//py/deps:reqs#flake8", - "//py/deps:reqs#packaging", - "//py/deps:reqs#yamllint", - "//py/deps:reqs#yapf", - "//py/deps:reqs#types-pyyaml", ], sources=[ "__init__.py", diff --git a/py/envoy.code.check/tests/BUILD b/py/envoy.code.check/tests/BUILD index c7903279e4..cfae6c771e 100644 --- a/py/envoy.code.check/tests/BUILD +++ b/py/envoy.code.check/tests/BUILD @@ -2,13 +2,6 @@ toolshed_tests( "envoy.code.check", dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-checker", "//py/envoy.base.utils/envoy/base/utils", - "//py/deps:reqs#flake8", - "//py/deps:reqs#packaging", - "//py/deps:reqs#pep8-naming", - "//py/deps:reqs#yapf", ], ) diff --git a/py/envoy.dependency.check/envoy/dependency/check/BUILD b/py/envoy.dependency.check/envoy/dependency/check/BUILD index 7baffabcf3..435d1629cd 100644 --- a/py/envoy.dependency.check/envoy/dependency/check/BUILD +++ b/py/envoy.dependency.check/envoy/dependency/check/BUILD @@ -1,17 +1,6 @@ toolshed_library( "envoy.dependency.check", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-api-github", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-checker", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#gidgethub", - "//py/deps:reqs#jinja2", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#packaging", - ], sources=[ "abstract/__init__.py", "abstract/checker.py", diff --git a/py/envoy.dependency.check/tests/BUILD b/py/envoy.dependency.check/tests/BUILD index 83b4d95802..6e597632f4 100644 --- a/py/envoy.dependency.check/tests/BUILD +++ b/py/envoy.dependency.check/tests/BUILD @@ -1,13 +1,2 @@ -toolshed_tests( - "envoy.dependency.check", - dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-api-github", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-checker", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#packaging", - ], -) +toolshed_tests("envoy.dependency.check") diff --git a/py/envoy.distribution.release/envoy/distribution/release/BUILD b/py/envoy.distribution.release/envoy/distribution/release/BUILD index 405c248668..676334840a 100644 --- a/py/envoy.distribution.release/envoy/distribution/release/BUILD +++ b/py/envoy.distribution.release/envoy/distribution/release/BUILD @@ -2,12 +2,7 @@ toolshed_library( "envoy.distribution.release", dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#envoy-base-utils", "//py/envoy.github.release/envoy/github/abstract", - "//py/deps:reqs#envoy-github-release", ], tags=["abstract-types"], ) diff --git a/py/envoy.distribution.release/tests/BUILD b/py/envoy.distribution.release/tests/BUILD index be909518b8..0733fac680 100644 --- a/py/envoy.distribution.release/tests/BUILD +++ b/py/envoy.distribution.release/tests/BUILD @@ -5,6 +5,5 @@ toolshed_tests( "//py/deps:reqs#abstracts", "//py/deps:reqs#aio-core", "//py/envoy.github.release/envoy/github/abstract", - "//py/deps:reqs#envoy-github-release", ], ) diff --git a/py/envoy.distribution.verify/envoy/distribution/verify/BUILD b/py/envoy.distribution.verify/envoy/distribution/verify/BUILD index cd9dd9fa6e..4786f78b36 100644 --- a/py/envoy.distribution.verify/envoy/distribution/verify/BUILD +++ b/py/envoy.distribution.verify/envoy/distribution/verify/BUILD @@ -2,8 +2,6 @@ toolshed_library( "envoy.distribution.verify", dependencies=[ - "//py/deps:reqs#aio-run-checker", - "//py/deps:reqs#envoy-base-utils", "//py/envoy.distribution.verify/envoy/distribution/distrotest", ], ) diff --git a/py/envoy.distribution.verify/tests/BUILD b/py/envoy.distribution.verify/tests/BUILD index 34dc4558b2..f224aae7e1 100644 --- a/py/envoy.distribution.verify/tests/BUILD +++ b/py/envoy.distribution.verify/tests/BUILD @@ -2,8 +2,6 @@ toolshed_tests( "envoy.distribution.verify", dependencies=[ - "//py/deps:reqs#aio-run-checker", - "//py/deps:reqs#envoy-base-utils", "//py/envoy.distribution.verify/envoy/distribution/distrotest", ], ) diff --git a/py/envoy.docker.utils/envoy/docker/utils/BUILD b/py/envoy.docker.utils/envoy/docker/utils/BUILD index 1c1b291e78..e827ab0858 100644 --- a/py/envoy.docker.utils/envoy/docker/utils/BUILD +++ b/py/envoy.docker.utils/envoy/docker/utils/BUILD @@ -1,6 +1,3 @@ toolshed_library( "envoy.docker.utils", - dependencies=[ - "//py/deps:reqs#aiodocker", - ], ) diff --git a/py/envoy.docs.sphinx_runner/envoy/docs/sphinx_runner/BUILD b/py/envoy.docs.sphinx_runner/envoy/docs/sphinx_runner/BUILD index a67266d7c6..d4a950bd00 100644 --- a/py/envoy.docs.sphinx_runner/envoy/docs/sphinx_runner/BUILD +++ b/py/envoy.docs.sphinx_runner/envoy/docs/sphinx_runner/BUILD @@ -1,24 +1,6 @@ toolshed_library( "envoy.docs.sphinx_runner", - dependencies=[ - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#colorama", - "//py/deps:reqs#docutils", - "//py/deps:reqs#packaging", - "//py/deps:reqs#protobuf", - "//py/deps:reqs#pygments", - "//py/deps:reqs#sphinx", - "//py/deps:reqs#sphinx-copybutton", - "//py/deps:reqs#sphinx-rtd-theme", - "//py/deps:reqs#sphinx-tabs", - "//py/deps:reqs#sphinxcontrib-jquery", - "//py/deps:reqs#sphinxcontrib-httpdomain", - "//py/deps:reqs#sphinxcontrib-serializinghtml", - "//py/deps:reqs#sphinxext-rediraffe", - "//py/deps:reqs#types-pygments", - ], sources=[ "__init__.py", "cmd.py", diff --git a/py/envoy.docs.sphinx_runner/tests/BUILD b/py/envoy.docs.sphinx_runner/tests/BUILD index 53157f651f..4832a16bc2 100644 --- a/py/envoy.docs.sphinx_runner/tests/BUILD +++ b/py/envoy.docs.sphinx_runner/tests/BUILD @@ -1,14 +1,2 @@ -toolshed_tests( - "envoy.docs.sphinx_runner", - dependencies=[ - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#colorama", - "//py/deps:reqs#sphinx", - "//py/deps:reqs#sphinx-copybutton", - "//py/deps:reqs#sphinxcontrib-httpdomain", - "//py/deps:reqs#sphinxcontrib-serializinghtml", - "//py/deps:reqs#sphinxext-rediraffe", - ], -) +toolshed_tests("envoy.docs.sphinx_runner") diff --git a/py/envoy.github.release/envoy/github/release/BUILD b/py/envoy.github.release/envoy/github/release/BUILD index 7648d0de8f..71f5f2877e 100644 --- a/py/envoy.github.release/envoy/github/release/BUILD +++ b/py/envoy.github.release/envoy/github/release/BUILD @@ -2,17 +2,7 @@ toolshed_library( "envoy.github.release", dependencies=[ - "//py/deps:reqs#abstracts", - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#aiohttp", - "//py/deps:reqs#aiofiles", - "//py/deps:reqs#gidgethub", - "//py/deps:reqs#packaging", - "//py/deps:reqs#verboselogs", "//py/envoy.github.release/envoy/github/abstract", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#types-aiofiles", ], tags=["abstract-types"], sources=[ diff --git a/py/envoy.github.release/tests/BUILD b/py/envoy.github.release/tests/BUILD index 36619b8008..925a9b4dbf 100644 --- a/py/envoy.github.release/tests/BUILD +++ b/py/envoy.github.release/tests/BUILD @@ -2,8 +2,6 @@ toolshed_tests( "envoy.github.release", dependencies=[ - "//py/deps:reqs#aio-core", - "//py/deps:reqs#aio-run-runner", "//py/envoy.github.release/envoy/github/abstract", ], ) diff --git a/py/envoy.gpg.sign/envoy/gpg/sign/BUILD b/py/envoy.gpg.sign/envoy/gpg/sign/BUILD index 4edd000084..891f24c93f 100644 --- a/py/envoy.gpg.sign/envoy/gpg/sign/BUILD +++ b/py/envoy.gpg.sign/envoy/gpg/sign/BUILD @@ -2,9 +2,6 @@ toolshed_library( "envoy.gpg.sign", dependencies=[ - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#envoy-base-utils", - "//py/deps:reqs#verboselogs", "//py/envoy.gpg.sign/envoy/gpg/identity:envoy.gpg.identity", ], ) diff --git a/py/envoy.gpg.sign/tests/BUILD b/py/envoy.gpg.sign/tests/BUILD index e43e7f8338..b92e8089fd 100644 --- a/py/envoy.gpg.sign/tests/BUILD +++ b/py/envoy.gpg.sign/tests/BUILD @@ -2,8 +2,6 @@ toolshed_tests( "envoy.gpg.sign", dependencies=[ - "//py/deps:reqs#aio-run-runner", - "//py/deps:reqs#envoy-base-utils", "//py/envoy.gpg.sign/envoy/gpg/identity:envoy.gpg.identity", ], ) diff --git a/py/mypy-abstracts/mypy_abstracts/BUILD b/py/mypy-abstracts/mypy_abstracts/BUILD index f87e7b4c77..5e8e0a9885 100644 --- a/py/mypy-abstracts/mypy_abstracts/BUILD +++ b/py/mypy-abstracts/mypy_abstracts/BUILD @@ -1,7 +1,4 @@ toolshed_library( "mypy-abstracts", - dependencies=[ - "//py/deps:reqs#mypy", - ], ) diff --git a/py/mypy-abstracts/tests/BUILD b/py/mypy-abstracts/tests/BUILD index 84184950d8..2e6d124e66 100644 --- a/py/mypy-abstracts/tests/BUILD +++ b/py/mypy-abstracts/tests/BUILD @@ -1,7 +1,2 @@ -toolshed_tests( - "mypy-abstracts", - dependencies=[ - "//py/deps:reqs#mypy", - ], -) +toolshed_tests("mypy-abstracts") diff --git a/py/pants-toolshed/macros.py b/py/pants-toolshed/macros.py index 14ca1ca607..7d1335b28c 100644 --- a/py/pants-toolshed/macros.py +++ b/py/pants-toolshed/macros.py @@ -87,11 +87,38 @@ def _publish_req_dependencies(namespace: str) -> list: ] +def _runtime_req_canonical_name(req_str: str) -> str: + """Extract canonical requirement name from a PEP 508 requirement string.""" + requirement_name_chars = [] + for char in req_str.strip(): + if char in " <>=!~[;": + break + requirement_name_chars.append(char) + return _canonical_name("".join(requirement_name_chars)) + + +def _runtime_req_deps(namespace: str) -> list: + """Map setup.cfg install_requires entries to deps-resolve req targets.""" + return [ + f"//py/deps:reqs#{_runtime_req_canonical_name(req_str)}" + for req_str in _setup_cfg_install_requires(namespace) + ] + + def toolshed_library( namespace: str, dependencies=None, # Optional[List] **kwargs) -> None: """Library of namespaced code that can be packaged""" + for dep in (dependencies or []): + if dep.startswith("//py/deps:reqs#"): + raise ValueError( + f"toolshed_library({namespace!r}): runtime dependencies must be " + f"declared in py/{namespace}/setup.cfg [options] install_requires, " + f"not on the library BUILD target. Offending dep: {dep!r}. " + f"This prevents pinned lockfile versions from leaking into the " + f"published wheel's Requires-Dist metadata." + ) resources( name="package_data", sources=["py.typed"]) @@ -169,6 +196,7 @@ def toolshed_tests( """Test library for a namespaced package""" dependencies = ( _dep_on_myself(namespace) + + _runtime_req_deps(namespace) + (dependencies or [])) # TODO: remove this if we add separate per-package diff --git a/py/pytest-abstracts/pytest_abstracts/BUILD b/py/pytest-abstracts/pytest_abstracts/BUILD index 3a01c00da2..0e4df2a638 100644 --- a/py/pytest-abstracts/pytest_abstracts/BUILD +++ b/py/pytest-abstracts/pytest_abstracts/BUILD @@ -1,7 +1,4 @@ toolshed_library( "pytest-abstracts", - dependencies=[ - "//py/deps:reqs#abstracts", - ], ) diff --git a/py/pytest-abstracts/tests/BUILD b/py/pytest-abstracts/tests/BUILD index b11df2f69c..774afe4659 100644 --- a/py/pytest-abstracts/tests/BUILD +++ b/py/pytest-abstracts/tests/BUILD @@ -1,10 +1,5 @@ -toolshed_tests( - "pytest-abstracts", - dependencies=[ - "//py/deps:reqs#abstracts", - ], -) +toolshed_tests("pytest-abstracts") python_test_utils( name="utils", From b65b83585e9bd35d25d9e94bd884c76062efe9dc Mon Sep 17 00:00:00 2001 From: Ryan Northey Date: Thu, 14 May 2026 20:59:25 +0000 Subject: [PATCH 3/3] ci/python: Add wheel metadata publishing validation Signed-off-by: Ryan Northey --- .github/workflows/py.yml | 4 + py/tools/publish_check/BUILD | 12 - py/tools/publish_check/README.md | 7 + .../publish_check/check_wheel_metadata.py | 123 ++++++++ .../test_publish_wheel_metadata.py | 269 ------------------ 5 files changed, 134 insertions(+), 281 deletions(-) create mode 100644 py/tools/publish_check/README.md create mode 100644 py/tools/publish_check/check_wheel_metadata.py delete mode 100644 py/tools/publish_check/test_publish_wheel_metadata.py diff --git a/.github/workflows/py.yml b/.github/workflows/py.yml index 787854e560..2c9fde2a1f 100644 --- a/.github/workflows/py.yml +++ b/.github/workflows/py.yml @@ -136,6 +136,10 @@ jobs: named-caches-hash: "${{ hashFiles('pants*toml') }}" - name: Run pants package run: "pants --colors package ::" + - name: Verify wheel METADATA matches setup.cfg + run: | + python -m pip install --quiet packaging + python py/tools/publish_check/check_wheel_metadata.py - name: Archive wheels uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/py/tools/publish_check/BUILD b/py/tools/publish_check/BUILD index 4edd8ceb24..28bd70faaf 100644 --- a/py/tools/publish_check/BUILD +++ b/py/tools/publish_check/BUILD @@ -34,15 +34,3 @@ python_tests( "//py/pytest-patches:build_artefacts", ], ) - -python_tests( - name="publish_wheel_metadata_check", - sources=["test_publish_wheel_metadata.py"], - skip_mypy=True, - runtime_package_dependencies=["//py/_test_publish_pkg:package"], - dependencies=[ - "//py/deps:reqs#packaging", - "//py/deps:reqs#pytest", - "//py/deps:reqs#pytest-asyncio", - ], -) diff --git a/py/tools/publish_check/README.md b/py/tools/publish_check/README.md new file mode 100644 index 0000000000..710324ee32 --- /dev/null +++ b/py/tools/publish_check/README.md @@ -0,0 +1,7 @@ +`check_wheel_metadata.py` is a post-build verification script for CI. + +It runs after `pants package ::` and validates built `dist/*.whl` metadata +against each package's `py/*/setup.cfg` `install_requires`. + +This check intentionally runs outside the pants test graph because it operates +on already-built wheel artifacts in `dist/` rather than on source targets. diff --git a/py/tools/publish_check/check_wheel_metadata.py b/py/tools/publish_check/check_wheel_metadata.py new file mode 100644 index 0000000000..4b6c50f07f --- /dev/null +++ b/py/tools/publish_check/check_wheel_metadata.py @@ -0,0 +1,123 @@ +"""Verify built wheels in dist/ have Requires-Dist matching setup.cfg. + +Runs after `pants package ::` in CI. Walks dist/*.whl, parses METADATA, +locates the package's setup.cfg, and asserts: + + set(Requires-Dist without `; extra == ...`) + == set(install_requires from setup.cfg) + +Exits non-zero with a per-package diff on mismatch. +""" + +import glob +import pathlib +import re +import sys +import zipfile +from collections import defaultdict +from email.parser import Parser + +from packaging.requirements import Requirement +from packaging.utils import canonicalize_name + + +def _norm(req_str: str) -> tuple[str, str]: + r = Requirement(req_str) + return canonicalize_name(r.name), str(r.specifier) + + +def _setup_cfg_install_requires(path: pathlib.Path) -> list[str]: + reqs, in_options, in_ir = [], False, False + for raw in path.read_text().splitlines(): + s = raw.strip() + if s.startswith("[") and s.endswith("]"): + in_options = s == "[options]" + in_ir = False + continue + if not in_options: + continue + if s.startswith("install_requires"): + in_ir = True + if "=" in raw: + v = raw.split("=", 1)[1].strip() + if v: + reqs.append(v) + continue + if in_ir: + if raw and not raw[0].isspace(): + in_ir = False + continue + if s and not s.startswith("#"): + reqs.append(s) + return reqs + + +def _wheel_runtime_requires(whl: pathlib.Path) -> list[str]: + with zipfile.ZipFile(whl) as zf: + meta = next(n for n in zf.namelist() if n.endswith(".dist-info/METADATA")) + msg = Parser().parsestr(zf.read(meta).decode()) + out = [] + for rd in msg.get_all("Requires-Dist") or []: + if ";" in rd and "extra" in rd.split(";", 1)[1]: + continue + out.append(rd.split(";", 1)[0].strip()) + return out + + +def _pkg_name_from_wheel(whl: pathlib.Path) -> str: + return whl.name.split("-")[0] + + +def _setup_cfg_for(dist_name: str) -> pathlib.Path | None: + for cfg in pathlib.Path("py").glob("*/setup.cfg"): + for line in cfg.read_text().splitlines(): + m = re.match(r"\s*name\s*=\s*(\S+)", line) + if m and canonicalize_name(m.group(1)) == canonicalize_name(dist_name): + return cfg + return None + + +def main() -> int: + failures: dict[str, list[str]] = defaultdict(list) + wheels = sorted(pathlib.Path(p) for p in glob.glob("dist/*.whl")) + if not wheels: + print("ERROR: no wheels found in dist/", file=sys.stderr) + return 2 + + for whl in wheels: + dist_name = _pkg_name_from_wheel(whl) + cfg = _setup_cfg_for(dist_name) + if cfg is None: + failures[whl.name].append( + f"no matching py/*/setup.cfg for dist name {dist_name!r}" + ) + continue + expected = {_norm(r) for r in _setup_cfg_install_requires(cfg)} + actual = {_norm(r) for r in _wheel_runtime_requires(whl)} + if expected == actual: + print(f"OK {whl.name}") + continue + extra = actual - expected + missing = expected - actual + if extra: + failures[whl.name].append( + f"unexpected Requires-Dist (leaked from pants/deps?): {sorted(extra)}" + ) + if missing: + failures[whl.name].append( + f"missing Requires-Dist (in setup.cfg but not in wheel): {sorted(missing)}" + ) + + if failures: + print("\nFAIL: wheel METADATA does not match setup.cfg", file=sys.stderr) + for whl, errs in failures.items(): + print(f" {whl}", file=sys.stderr) + for e in errs: + print(f" - {e}", file=sys.stderr) + return 1 + print(f"\nAll {len(wheels)} wheels match their setup.cfg install_requires.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/py/tools/publish_check/test_publish_wheel_metadata.py b/py/tools/publish_check/test_publish_wheel_metadata.py deleted file mode 100644 index b645399949..0000000000 --- a/py/tools/publish_check/test_publish_wheel_metadata.py +++ /dev/null @@ -1,269 +0,0 @@ -"""End-to-end test: verify the built wheel METADATA for the test fixture -package. - -This test builds the ``toolshed-test-publish-pkg`` wheel via -``runtime_package_dependencies`` (pants produces the wheel before the test -sandbox is created) then cracks it open and asserts that: - -- Name, Version, Summary, Requires-Python match setup.cfg exactly. -- Classifier lines include the expected values. -- Requires-Dist contains the **loose** specifiers from setup.cfg - ``install_requires`` via synthetic publish requirement targets, NOT the - pinned ``==`` versions from the dev resolve. -- No extra packages leak in from the dev resolve (count check). -- The ``dev`` extras_require group appears as Provides-Extra / Requires-Dist. -- entry_points.txt in the dist-info has the expected console_scripts entry. - -The test intentionally fails if the toolshed_package macro wires pinned dev -deps into the wheel instead of the per-package setup.cfg ranges. -""" - -import glob -import os -import pathlib -import zipfile -from email.message import Message -from email.parser import Parser -from typing import List, Tuple - -import pytest -from packaging.requirements import Requirement -from packaging.utils import canonicalize_name - - -# --------------------------------------------------------------------------- -# Expected values — must match py/_test_publish_pkg/setup.cfg exactly -# --------------------------------------------------------------------------- - -_EXPECTED_NAME = "toolshed-test-publish-pkg" -_EXPECTED_VERSION = "0.0.1" -_EXPECTED_SUMMARY = "Toolshed test fixture for publish metadata validation" -_EXPECTED_PYTHON_REQUIRES = ">=3.12" - -_EXPECTED_CLASSIFIERS = [ - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3.12", -] - -# These must be LOOSE ranges, not == pins. -_EXPECTED_INSTALL_REQUIRES = [ - "aiohttp>=3.8.1", - "packaging>=23.0", - "pyyaml", -] - -_EXPECTED_EXTRA = "dev" -_EXPECTED_CONSOLE_SCRIPT = "toolshed-test-publish-pkg" - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _find_wheel() -> pathlib.Path: - """Locate the built wheel in the pants test sandbox. - - pants places ``runtime_package_dependencies`` artefacts at their normal - dist/ output path but with the ``dist/`` prefix stripped. For the target - at ``//py/_test_publish_pkg:package`` the wheel lands at:: - - py/_test_publish_pkg/toolshed_test_publish_pkg--*.whl - - We glob broadly in case the exact sub-path differs between pants versions. - """ - candidates = ( - glob.glob("py/_test_publish_pkg/*.whl") - + glob.glob("**/*.whl", recursive=True) - ) - whl_files = [ - p for p in candidates - if "toolshed_test_publish_pkg" in os.path.basename(p) - or "toolshed-test-publish-pkg" in os.path.basename(p) - ] - if not whl_files: - all_whl = glob.glob("**/*.whl", recursive=True) - pytest.fail( - f"Could not find toolshed_test_publish_pkg wheel.\n" - f"cwd={os.getcwd()!r}\n" - f"all .whl files found: {all_whl}") - return pathlib.Path(whl_files[0]) - - -def _read_metadata(wheel_path: pathlib.Path) -> Tuple[Message, List[str]]: - """Open the wheel zip and return the parsed METADATA Message.""" - with zipfile.ZipFile(wheel_path) as zf: - metadata_names = [ - n for n in zf.namelist() - if n.endswith("/METADATA") and ".dist-info/" in n - ] - assert len(metadata_names) == 1, ( - f"Expected exactly one METADATA in {wheel_path.name}, " - f"found: {metadata_names}") - return Parser().parsestr( - zf.read(metadata_names[0]).decode("utf-8")), zf.namelist() - - -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - -def test_wheel_basic_metadata() -> None: - """Name, Version, Summary, Requires-Python match setup.cfg.""" - wheel_path = _find_wheel() - msg, _ = _read_metadata(wheel_path) - - assert msg["Name"] == _EXPECTED_NAME, ( - f"Name mismatch: expected {_EXPECTED_NAME!r}, got {msg['Name']!r}") - assert msg["Version"] == _EXPECTED_VERSION, ( - f"Version mismatch: expected {_EXPECTED_VERSION!r}, " - f"got {msg['Version']!r}") - assert msg["Summary"] == _EXPECTED_SUMMARY, ( - f"Summary mismatch: expected {_EXPECTED_SUMMARY!r}, " - f"got {msg['Summary']!r}") - assert msg["Requires-Python"] == _EXPECTED_PYTHON_REQUIRES, ( - f"Requires-Python mismatch: expected {_EXPECTED_PYTHON_REQUIRES!r}, " - f"got {msg['Requires-Python']!r}") - - -def test_wheel_classifiers() -> None: - """Classifier lines include the expected values from setup.cfg.""" - wheel_path = _find_wheel() - msg, _ = _read_metadata(wheel_path) - - classifiers: List[str] = msg.get_all("Classifier") or [] - for expected in _EXPECTED_CLASSIFIERS: - assert expected in classifiers, ( - f"Expected classifier {expected!r} not found.\n" - f"All classifiers: {classifiers}") - - -def test_wheel_requires_dist_loose_ranges() -> None: - """Requires-Dist must contain LOOSE ranges, never == pins. - - This is the key test: if the toolshed_package macro mistakenly wires - the pinned //py/deps:reqs#* targets into the wheel instead of (or in - addition to) the synthetic per-package publish requirement targets, the - Requires-Dist - will contain ``==`` specifiers and this test will fail. - """ - wheel_path = _find_wheel() - msg, _ = _read_metadata(wheel_path) - - all_requires_dist: List[str] = msg.get_all("Requires-Dist") or [] - full_block = "\n".join(f" Requires-Dist: {r}" for r in all_requires_dist) - - # Split into main (unconditional) and extra-gated deps. - main_requires = [ - r for r in all_requires_dist - if "; extra ==" not in r - ] - - actual_requires: dict = { - canonicalize_name(Requirement(r).name): Requirement(r) - for r in main_requires - } - - for expected_str in _EXPECTED_INSTALL_REQUIRES: - expected = Requirement(expected_str) - canon = canonicalize_name(expected.name) - - assert canon in actual_requires, ( - f"Expected {expected_str!r} in Requires-Dist (unconditional) " - f"but not found.\n" - f"All Requires-Dist:\n{full_block}") - - actual = actual_requires[canon] - - # Check the specifier matches the loose range, not a pin. - if str(expected.specifier): - assert str(actual.specifier) == str(expected.specifier), ( - f"Specifier mismatch for {expected.name!r}: " - f"expected {str(expected.specifier)!r}, " - f"got {str(actual.specifier)!r}.\n" - f"All Requires-Dist:\n{full_block}") - - # The key assertion: no == operator in any specifier. - for spec in actual.specifier: - assert spec.operator != "==", ( - f"Wheel has pinned dep {actual.name}{actual.specifier} " - f"(operator ==) — expected a loose range.\n" - f"This indicates the toolshed_package macro is walking " - f"pinned dev deps into the wheel instead of " - f"setup.cfg ranges.\n" - f"All Requires-Dist:\n{full_block}") - - -def test_wheel_requires_dist_no_extra_packages() -> None: - """Main Requires-Dist must contain exactly the setup.cfg install_requires - packages. - - No extra packages from the dev resolve should leak into the wheel. - """ - wheel_path = _find_wheel() - msg, _ = _read_metadata(wheel_path) - - all_requires_dist: List[str] = msg.get_all("Requires-Dist") or [] - full_block = "\n".join(f" Requires-Dist: {r}" for r in all_requires_dist) - - main_requires = [ - r for r in all_requires_dist - if "; extra ==" not in r - ] - - expected_canon = { - canonicalize_name(Requirement(r).name) - for r in _EXPECTED_INSTALL_REQUIRES - } - actual_canon = { - canonicalize_name(Requirement(r).name) - for r in main_requires - } - - assert actual_canon == expected_canon, ( - f"Unexpected packages in main Requires-Dist.\n" - f"Expected: {sorted(expected_canon)}\n" - f"Got: {sorted(actual_canon)}\n" - f"Extra: {sorted(actual_canon - expected_canon)}\n" - f"Missing: {sorted(expected_canon - actual_canon)}\n" - f"All Requires-Dist:\n{full_block}") - - -def test_wheel_extras_require() -> None: - """The 'dev' extra must appear as Provides-Extra and Requires-Dist.""" - wheel_path = _find_wheel() - msg, _ = _read_metadata(wheel_path) - - provides_extra: List[str] = msg.get_all("Provides-Extra") or [] - assert _EXPECTED_EXTRA in provides_extra, ( - f"Expected Provides-Extra: {_EXPECTED_EXTRA!r}, " - f"got: {provides_extra}") - - all_requires_dist: List[str] = msg.get_all("Requires-Dist") or [] - dev_reqs = [ - r for r in all_requires_dist - if f'extra == "{_EXPECTED_EXTRA}"' in r - or f"extra == '{_EXPECTED_EXTRA}'" in r - ] - assert dev_reqs, ( - f"Expected at least one Requires-Dist for extra={_EXPECTED_EXTRA!r}, " - f"found none.\n" - f"All Requires-Dist: {all_requires_dist}") - - -def test_wheel_entry_points() -> None: - """entry_points.txt must contain the expected console_scripts entry.""" - wheel_path = _find_wheel() - _, all_names = _read_metadata(wheel_path) - - ep_names = [n for n in all_names if n.endswith("entry_points.txt")] - assert ep_names, ( - f"No entry_points.txt found in {wheel_path.name}.\n" - f"Files: {[n for n in all_names if '.dist-info/' in n]}") - - with zipfile.ZipFile(wheel_path) as zf: - ep_text = zf.read(ep_names[0]).decode("utf-8") - - assert _EXPECTED_CONSOLE_SCRIPT in ep_text, ( - f"Expected console_script {_EXPECTED_CONSOLE_SCRIPT!r} in " - f"entry_points.txt.\n" - f"entry_points.txt content:\n{ep_text}")