Repository navigation
Add PyPI wheel publishing flow #4656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d5304bf
a5a0082
f6d267e
daf25ab
c37895c
0a29882
98c3489
0d8923e
0293575
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # NOTE: This filename is hardcoded in our trusted publishing setup on pypi.org and test.pypi.org; | ||
| # do not change this filename without updating it in both places as well. | ||
| name: Publish to PyPI | ||
| on: | ||
| workflow_dispatch: | ||
| release: | ||
| types: [published] | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 | ||
|
|
||
| - run: uv sync | ||
|
|
||
| - run: uv run python scripts/generate_wheels.py | ||
|
|
||
| - uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: wheels | ||
| path: dist/ | ||
|
|
||
| test-publish: | ||
| if: github.event_name == 'workflow_dispatch' | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| environment: pypi-test | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - uses: actions/download-artifact@v8 | ||
| with: | ||
| name: wheels | ||
| path: dist/ | ||
|
|
||
| - uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| repository-url: https://test.pypi.org/legacy/ | ||
| skip-existing: true | ||
|
|
||
| publish: | ||
| if: github.event_name != 'workflow_dispatch' | ||
| runs-on: ubuntu-latest | ||
| environment: pypi-release | ||
| permissions: | ||
| id-token: write | ||
| steps: | ||
| - uses: actions/download-artifact@v8 | ||
| with: | ||
| name: wheels | ||
| path: dist/ | ||
|
|
||
| - uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| skip-existing: true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .venv | ||
| .ruff_cache | ||
| dist | ||
| uv.lock |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../LICENSE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../README.md |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Copyright 2020-2026 Buf Technologies, Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # buf-bin: Buf CLI distributed via PyPI. | ||
| # The buf binary is installed directly as a script on PATH. |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why we need to keep the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So I added a doc into this file to comment on this since indeed that wasn't obvious. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Allow uv sync to download tools for generating binaries to this directory for release | ||
| since it would fail on an empty directory otherwise. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| [project] | ||
| name = "buf-bin" | ||
| version = "1.72.0" | ||
| description = "Buf CLI distributed via PyPI" | ||
| readme = "README.md" | ||
| requires-python = ">=3.10" | ||
| license = "Apache-2.0" | ||
| license-files = ["LICENSE"] | ||
| keywords = [ | ||
| "buf", | ||
| "bufbuild", | ||
| "protobuf", | ||
| "protoc", | ||
| ] | ||
| classifiers = [ | ||
| "Development Status :: 5 - Production/Stable", | ||
| "Environment :: Console", | ||
| "Intended Audience :: Developers", | ||
| "Operating System :: OS Independent", | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Python :: 3 :: Only", | ||
| "Topic :: Software Development :: Build Tools", | ||
| "Topic :: Software Development :: Code Generators", | ||
| "Topic :: Software Development :: Compilers", | ||
| ] | ||
|
|
||
| [project.urls] | ||
| Changelog = "https://github.com/bufbuild/buf/blob/main/CHANGELOG.md" | ||
| Documentation = "https://buf.build/docs/cli/" | ||
| Homepage = "https://buf.build/docs/cli/" | ||
| Issue = "https://github.com/bufbuild/buf/issues" | ||
| Repository = "https://github.com/bufbuild/buf" | ||
|
|
||
| [dependency-groups] | ||
| dev = ["wheel"] | ||
|
|
||
| [build-system] | ||
| requires = ["uv_build>=0.11.0,<0.12.0"] | ||
| build-backend = "uv_build" | ||
|
|
||
| [tool.uv.build-backend] | ||
| module-name = "buf_bin" | ||
| module-root = "" | ||
| data = { scripts = "out/bin" } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| # Copyright 2020-2026 Buf Technologies, Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import hashlib | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| import urllib.request | ||
| from importlib.metadata import version as pkg_version | ||
| from pathlib import Path | ||
|
|
||
| GITHUB_RELEASES_BASE = "https://github.com/bufbuild/buf/releases/download" | ||
|
|
||
| # Maps buf platform suffix to Python wheel platform tag. | ||
| # buf binary names on GitHub releases follow the pattern: | ||
| # buf-{PLATFORM} or buf-{PLATFORM}.exe | ||
| # Check https://go.dev/wiki/MinimumRequirements#operating-systems for | ||
| # minimum OS versions, especially macOS. | ||
| PLATFORMS = [ | ||
| ("Darwin-arm64", "macosx_11_0_arm64"), | ||
| ("Darwin-x86_64", "macosx_11_0_x86_64"), | ||
| ("Linux-aarch64", "manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64"), | ||
| ("Linux-armv7", "manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l"), | ||
| ("Linux-ppc64le", "manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le"), | ||
| ("Linux-riscv64", "manylinux_2_17_riscv64.musllinux_1_2_riscv64"), | ||
| ("Linux-s390x", "manylinux_2_17_s390x.manylinux2014_s390x.musllinux_1_1_s390x"), | ||
| ("Linux-x86_64", "manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64"), | ||
| ("Windows-arm64", "win_arm64"), | ||
| ("Windows-x86_64", "win_amd64"), | ||
| ] | ||
|
|
||
|
|
||
| def fetch_checksums(version: str) -> dict[str, str]: | ||
| url = f"{GITHUB_RELEASES_BASE}/v{version}/sha256.txt" | ||
| with urllib.request.urlopen(url) as response: | ||
| content = response.read().decode() | ||
| result = {} | ||
| for line in content.splitlines(): | ||
| if " " in line: | ||
| sha256, filename = line.split(" ", 1) | ||
| result[filename] = sha256 | ||
| return result | ||
|
|
||
|
|
||
| def verify_checksum(path: Path, expected: str) -> None: | ||
| actual = hashlib.sha256(path.read_bytes()).hexdigest() | ||
| if actual != expected: | ||
| msg = f"checksum mismatch for {path.name}: expected {expected}, got {actual}" | ||
| raise ValueError(msg) | ||
|
|
||
|
|
||
| def download(url: str, dest: Path) -> None: | ||
| with urllib.request.urlopen(url) as response, dest.open("wb") as f: | ||
| shutil.copyfileobj(response, f) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| base_dir = Path(__file__).parent.parent | ||
|
|
||
| version = pkg_version("buf-bin") | ||
|
|
||
| print(f"Generating wheels for buf v{version}") | ||
| checksums = fetch_checksums(version) | ||
|
|
||
| bin_dir = base_dir / "out" / "bin" | ||
|
|
||
| for buf_platform, wheel_platform in PLATFORMS: | ||
| print(f"\nBuilding wheel for {buf_platform} ({wheel_platform})") | ||
|
|
||
| shutil.rmtree(bin_dir, ignore_errors=True) | ||
| bin_dir.mkdir(parents=True) | ||
|
|
||
| try: | ||
| ext = ".exe" if buf_platform.startswith("Windows") else "" | ||
| filename = f"buf-{buf_platform}{ext}" | ||
| dest = bin_dir / f"buf{ext}" | ||
| print(f" Downloading {GITHUB_RELEASES_BASE}/v{version}/{filename}") | ||
| download(f"{GITHUB_RELEASES_BASE}/v{version}/{filename}", dest) | ||
| verify_checksum(dest, checksums[filename]) | ||
| if not ext: | ||
| dest.chmod(0o755) | ||
|
|
||
| subprocess.run( | ||
| ["uv", "build", "--wheel"], | ||
| check=True, | ||
| cwd=base_dir, | ||
| ) | ||
|
|
||
| dist_dir = base_dir / "dist" | ||
| built_wheel = next(dist_dir.glob("*-py3-none-any.whl")) | ||
|
|
||
| subprocess.run( | ||
| [ | ||
| sys.executable, | ||
| "-m", | ||
| "wheel", | ||
| "tags", | ||
| "--remove", | ||
| "--platform-tag", | ||
| wheel_platform, | ||
| str(built_wheel), | ||
| ], | ||
| check=True, | ||
| ) | ||
| finally: | ||
| shutil.rmtree(bin_dir, ignore_errors=True) | ||
|
|
||
| bin_dir.mkdir(parents=True) | ||
| (bin_dir / ".gitkeep").touch() | ||
|
|
||
| print("\nDone. Wheels written to dist/") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the repo style of floating tags as much as possible, but uv only publishes pins, no floating tag, so this line will probably result in an increase in dependabot PRs