Distributing Go Binaries Through PyPI: Why This Changes Everything

4 min read

What if you could install a Go CLI tool with just uvx tool-name? Simon Willison figured out how to publish cross-platform Go binaries to PyPI, and the implications for developer tooling are significant. Your Go code can now be a dependency in Python projects.

The Core Insight

The trick is leveraging Python’s wheel packaging system. PyPI already handles platform-specific binary distribution through wheel filename conventions:

sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl
sqlite_scanner-0.1.1-py3-none-manylinux_2_17_x86_64.whl
sqlite_scanner-0.1.1-py3-none-win_amd64.whl

When you run pip install sqlite-scanner or uvx sqlite-scanner, Python’s packaging machinery automatically selects the correct wheel for your OS and architecture. Inside each wheel is the compiled Go binary plus a thin Python wrapper that locates and executes it.

The wrapper is minimal:

def main():
    binary = get_binary_path()
    if sys.platform == "win32":
        sys.exit(subprocess.call([binary] + sys.argv[1:]))
    else:
        os.execvp(binary, [binary] + sys.argv[1:])

To automate this process, Willison built go-to-wheel:

uvx go-to-wheel ~/dev/sqlite-scanner \
  --set-version-var main.version \
  --version 0.1.1 \
  --readme README.md \
  --author 'Simon Willison' \
  --url https://github.com/simonw/sqlite-scanner \
  --description 'Scan directories for SQLite databases'

This generates wheels for all major platforms, ready to upload with twine upload dist/*.

Why This Matters

The real power here is Go binaries as Python dependencies. Consider this pattern:

# pyproject.toml
dependencies = ["sqlite-scanner"]

# Your Python code
import sqlite_scanner
result = subprocess.run([sqlite_scanner.get_binary_path(), "--json", "."])

This enables a new architecture: write performance-critical or specialized functionality in Go, then consume it seamlessly from Python projects. Users never need to install Go, configure GOPATH, or compile from source. They just pip install.

Willison demonstrates this with datasette-scan—a Datasette plugin that depends on his sqlite-scanner Go binary to find SQLite databases on disk:

uv run --with datasette-scan datasette scan ~/Downloads

Python is excellent at orchestration, subprocess management, and user interfaces. Go excels at concurrent filesystem operations, network protocols, and memory-efficient processing. This pattern lets you use each language where it shines.

The approach isn’t limited to Go either. The same wheel-based distribution works for any compiled binary. Willison mentions static-ffmpeg—a PyPI package that bundles ffmpeg binaries so Python scripts can depend on multimedia processing without requiring users to install ffmpeg separately.

Key Takeaways

  • PyPI handles cross-platform distribution: Wheel filenames encode OS/arch, pip/uv select automatically
  • go-to-wheel automates packaging: Single command generates all platform wheels from a Go project
  • Binaries become dependencies: Go tools can be listed in pyproject.toml and consumed programmatically
  • Users never see Go: pip install or uvx just works, no Go toolchain required
  • Works with any compiled binary: Same pattern applies to Rust, C, or any language that produces executables
  • macOS code signing handled: Unsigned binaries require gatekeeper bypass, but distribution still works

Looking Ahead

This pattern points toward a future of polyglot dependency graphs. Today’s Python project might depend on:
– Go binaries for concurrent I/O operations
– Rust binaries for memory-safe parsing
– Bundled ffmpeg for media processing
– SQLite extensions compiled from C

The key enabler is that modern Python packaging (pip, uv, PyPI) already solved the hard problems of cross-platform binary distribution. We’re just now realizing we can piggyback other ecosystems onto that infrastructure.

For Go developers: if you’re building CLI tools, consider publishing to PyPI alongside GitHub releases. You’ll reach users who would never go install but happily uvx everything.

For Python developers: start thinking of Go as a powerful “library language” for performance-critical components. The subprocess boundary is thin, and the distribution story is now solved.


Based on analysis of Distributing Go binaries like sqlite-scanner through PyPI using go-to-wheel


Share this article

Related Articles