Doramagic Project Pack · Human Manual

go-to-wheel

Related topics: Installation, Quick Start Guide

Introduction

Related topics: Installation, Quick Start Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section What go-to-wheel Does

Continue reading this section for the full explanation and source context.

Section How It Works

Continue reading this section for the full explanation and source context.

Section Platform Mapping

Continue reading this section for the full explanation and source context.

Related topics: Installation, Quick Start Guide

Introduction

Overview

go-to-wheel is a Python command-line tool that compiles Go CLI programs into Python wheels, enabling Go binaries to be distributed and installed through the Python packaging ecosystem via pip or pipx. This tool bridges the Go and Python communities by providing a seamless way to package and distribute Go applications to Python developers who are accustomed to installing tools through Python package managers. The project was created to address the gap in the ecosystem—there was no equivalent to Rust's maturin --bindings bin for Go, and go-to-wheel fills that void by providing a straightforward solution for bundling Go binaries into standard Python wheel packages.

Sources: README.md:1-10

The tool takes a Go module directory as input, cross-compiles the Go binary for multiple target platforms, and produces properly-tagged Python wheels that can be installed via standard Python package management tools. This approach allows Go developers to leverage the extensive Python packaging infrastructure for distribution, including PyPI hosting, pipx for isolated installations, and standard Python dependency resolution.

Sources: spec.md:1-15

Core Functionality

What go-to-wheel Does

At its core, go-to-wheel performs three main operations to transform a Go module into a distributable Python wheel. First, it validates that the input directory is a valid Go module containing a go.mod file. Second, it cross-compiles the Go binary for each requested target platform using environment variables GOOS and GOARCH with CGO_ENABLED=0 to produce static binaries. Third, it creates a Python package structure with a thin wrapper that executes the bundled binary, then packages everything into a wheel file following PEP 427 conventions.

Sources: spec.md:80-95

The resulting wheel can be installed with pip, which extracts the bundled Go binary and creates console entry points that make the tool available on the system PATH. This means users can install and run Go-compiled tools exactly as they would any other Python package, without needing to understand that the underlying implementation is written in Go.

Sources: README.md:55-65

How It Works

The build process follows a precise sequence of operations to ensure compatibility across all supported platforms. The tool begins by validating the input Go module directory and verifying that Go is installed and accessible. It then iterates through each requested platform, setting the appropriate environment variables and running the Go compiler with flags optimized for static binary production.

graph TD
    A[Start: go-to-wheel] --> B{Validate Go Module}
    B -->|go.mod exists| C[Parse Package Metadata]
    B -->|No go.mod| E[Error: Not a Go module]
    C --> D{For each target platform}
    D --> F[Cross-compile with GOOS/GOARCH]
    F --> G[CGO_ENABLED=0 for static binary]
    G --> H[Generate Python wrapper]
    H --> I[Create wheel structure]
    I --> J[Zip into .whl file]
    J --> D
    D -->|All platforms done| K[Output wheels to ./dist]
    K --> L[Success]

Sources: spec.md:85-100

The cross-compilation step uses CGO_ENABLED=0 to ensure that the resulting binaries are fully static and have no libc dependencies, which is essential for compatibility across different Linux distributions and container environments. The -ldflags="-s -w" flags strip debug information and reduce binary size for more efficient distribution.

Sources: README.md:45-50

Supported Platforms

go-to-wheel supports a comprehensive range of target platforms across Linux, macOS, and Windows operating systems, covering both x86_64 and ARM architectures. The tool provides different wheel tags depending on whether the target uses glibc (standard Linux) or musl (Alpine Linux and similar distributions).

Platform Mapping

Target PlatformGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: README.md:35-45

Default Platform Behavior

By default, go-to-wheel builds wheels for all eight supported platforms, ensuring maximum compatibility for distribution. Users can optionally specify a subset of platforms using the --platforms flag with a comma-separated list of target platforms, which is useful when building for specific deployment environments or when cross-compilation toolchains are not available for all targets.

Sources: spec.md:40-45

Installation and Requirements

Prerequisites

The tool itself requires Python 3.10 or later and has no external Python dependencies—it uses only the Python standard library. For building Go binaries, Go 1.16 or later is required due to the use of go mod commands.

Sources: spec.md:115-120

Installation Methods

go-to-wheel can be installed using standard Python package installation tools:

pip install go-to-wheel
# or
pipx install go-to-wheel

Sources: README.md:20-25

After installation, Go must be available in the system PATH. The tool will automatically detect the Go binary or can be configured to use a specific path via the --go-binary option.

Command Line Interface

Basic Usage

The simplest usage of go-to-wheel requires only a path to the Go module directory:

go-to-wheel path/to/go-module

Sources: README.md:30-35

This command creates wheels in the ./dist directory for all supported platforms using default metadata values.

Command Options

OptionDescriptionDefault
--name NAMEPython package nameDirectory basename
--version VERSIONPackage version0.1.0
--output-dir DIRDirectory for built wheels./dist
--entry-point NAMECLI command nameSame as package name
--platforms PLATFORMSComma-separated list of targetsAll supported platforms
--go-binary PATHPath to Go binarygo
--description TEXTPackage description"Go binary packaged as Python wheel"
--license LICENSELicense identifierNone
--author AUTHORAuthor nameNone
--author-email EMAILAuthor emailNone
--url URLProject URLNone
--requires-python VERSIONPython version requirement>=3.10
--readme PATHPath to README markdown fileNone
--set-version-var VARGo variable for version via -X ldflagNone
--ldflags FLAGSAdditional Go linker flagsNone

Sources: spec.md:25-45

Wheel Structure

Each generated wheel follows PEP 427 format with a specific internal structure that enables proper execution of the bundled Go binary.

File Structure

{package_name}-{version}-py3-none-{platform_tag}.whl
├── {package_name}/
│   ├── __init__.py
│   ├── __main__.py
│   └── bin/
│       └── {binary_name}[.exe]
├── {package_name}-{version}.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── entry_points.txt

Sources: spec.md:60-70

Python Wrapper Mechanism

The Python wrapper in __init__.py provides the execution mechanism for the bundled binary. It uses os.execvp() on Unix systems to replace the Python process with the Go binary, ensuring proper signal handling and exit code propagation. On Windows, it uses subprocess.call() to achieve similar behavior with proper signal handling.

Sources: spec.md:60-90

def main():
    """Execute the bundled binary."""
    binary = get_binary_path()
    if sys.platform == "win32":
        # On Windows, use subprocess to properly handle signals
        sys.exit(subprocess.call([binary] + sys.argv[1:]))
    else:
        # On Unix, exec replaces the process
        os.execvp(binary, [binary] + sys.argv[1:])

Sources: spec.md:75-85

Why Python Wrapper vs .data/scripts

The specification uses a Python wrapper with console_scripts entry point rather than placing the binary directly in .data/scripts/ for several important reasons. First, it provides consistent behavior across all platforms without platform-specific edge cases. Second, it enables better error messages if the binary is missing or incompatible with the system. Third, it offers future flexibility for adding Python-side features such as version checking or update notifications. Fourth, it works seamlessly with pipx install for isolated application installations.

Sources: spec.md:90-100

Use Cases

Distributing Go Tools to Python Users

The primary use case for go-to-wheel is distributing Go CLI tools to Python developers who prefer to use pip or pipx for managing command-line tools. This is particularly valuable for tools that have natural appeal to the Python community or tools that need to be installed alongside Python packages as dependencies.

Sources: README.md:8-12

PyPI Distribution

Go binaries packaged as wheels can be uploaded to PyPI, making them available through the standard Python package index. This enables distribution to millions of Python developers who can install the tool with a single pip install command, without needing to understand Go compilation or maintain separate release artifacts.

pipx Isolation

Wheels built with go-to-wheel work seamlessly with pipx, which provides isolated Python environments for command-line tools. Users can install Go-compiled tools in isolation to avoid dependency conflicts:

pipx install ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl

Sources: README.md:60-65

Advanced Features

Version Embedding

Go-to-wheel supports embedding the package version into the Go binary at compile time using Go linker flags. This requires a var version declaration in the Go source code. When the --set-version-var option is used, the tool automatically passes the value from --version to the Go linker via the -X flag.

Sources: README.md:50-55

A typical Go pattern for version embedding:

var version = "dev"

func main() {
    if os.Args[1] == "--version" {
        fmt.Println(version) // prints "2.0.0" when built with --set-version-var
    }
}

Sources: README.md:50-60

Custom Linker Flags

Additional Go linker flags can be passed using the --ldflags option, which are appended to the default -s -w flags. This allows for custom version strings, commit hashes, or other build-time information:

go-to-wheel ./mytool --version 2.0.0 \
  --ldflags "-X main.version=2.0.0 -X main.commit=abc123"

Sources: README.md:60-65

README Integration

The --readme option allows embedding a README markdown file into the wheel's METADATA, which is displayed on the PyPI package page:

go-to-wheel ./mytool --readme README.md

Sources: spec.md:42

Development

Running Tests

The project uses pytest for testing. After cloning the repository, tests can be run with:

git clone https://github.com/simonw/go-to-wheel
cd go-to-wheel
uv run pytest

Sources: README.md:70-75

Project Architecture

The tool is implemented as a single Python module (go_to_wheel/__init__.py) with no external dependencies. The main components include argument parsing, cross-compilation orchestration, wheel file generation, and metadata file creation. This simple architecture makes the tool easy to understand, maintain, and extend.

go-to-wheel was inspired by similar tools in the Rust ecosystem. The primary inspiration is maturin, which provides the same functionality for Rust programs with Python bindings. Additionally, pip-binary-factory serves as a template for packaging pre-built binaries.

Sources: README.md:75-80

ToolLanguageRepository
go-to-wheelGosimonw/go-to-wheel
maturinRustPyO3/maturin
pip-binary-factoryTemplateBing-su/pip-binary-factory

License

go-to-wheel is released under the Apache 2.0 license, allowing for both personal and commercial use with minimal restrictions.

Sources: README.md:12

Summary

go-to-wheel provides a valuable bridge between the Go and Python ecosystems by enabling Go CLI programs to be packaged and distributed as standard Python wheels. With support for eight target platforms, flexible metadata configuration, and seamless integration with pip and pipx, it offers Go developers a straightforward path to reaching Python's extensive user base. The tool's single-file implementation with no external dependencies ensures reliability and ease of installation, making it a practical choice for distributing Go tools to the Python community.

Sources: README.md:1-10

Installation

Related topics: Introduction, Development Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Python Dependencies

Continue reading this section for the full explanation and source context.

Section Go Installation Verification

Continue reading this section for the full explanation and source context.

Section Via pip (Recommended for Users)

Continue reading this section for the full explanation and source context.

Related topics: Introduction, Development Guide

Installation

go-to-wheel is a Python tool that compiles Go CLI programs into Python wheels. Installing this tool correctly is the first step to packaging Go binaries for PyPI distribution.

Prerequisites

Before installing go-to-wheel, ensure your environment meets the following requirements:

RequirementVersionDescription
Python>= 3.10The tool is implemented in Python and requires this version or higher
Go>= 1.16Required for building Go modules with go mod support

Sources: spec.md

Python Dependencies

go-to-wheel has no external Python dependencies. The tool uses only Python's standard library for all operations:

  • argparse - Command-line argument parsing
  • zipfile - Wheel creation
  • subprocess - Go compilation execution
  • hashlib / base64 - RECORD file hash generation
  • tempfile / shutil - Temporary directory management

Sources: go_to_wheel/__init__.py:1-20

Go Installation Verification

To verify Go is installed and accessible:

go version

Ensure go is in your system's PATH environment variable.

Installation Methods

pip install go-to-wheel

Sources: README.md

This method installs go-to-wheel globally in your Python environment.

pipx install go-to-wheel

Sources: README.md

pipx is preferred for CLI applications because it:

  • Creates an isolated virtual environment for the tool
  • Automatically manages PATH shims for the installed command
  • Avoids dependency conflicts with other Python packages

Installation Workflow

graph TD
    A[Choose Installation Method] --> B{pip or pipx?}
    B -->|pip| C[Run pip install go-to-wheel]
    B -->|pipx| D[Run pipx install go-to-wheel]
    C --> E[Download from PyPI]
    D --> E
    E --> F[Install to Python environment]
    F --> G[Create executable entry point]
    G --> H[go-to-wheel ready to use]

Post-Installation Verification

After installation, verify the tool is working:

go-to-wheel --version

Expected output:

go-to-wheel v0.1.0

Sources: go_to_wheel/__init__.py

Usage Requirements

Once installed, you need a Go module to package:

# Verify you have a Go module
cd path/to/your-go-module
ls go.mod

The tool will:

  1. Cross-compile the Go binary for multiple platforms
  2. Create Python wheels with proper metadata
  3. Bundle everything into installable packages

Sources: spec.md

Quick Start After Installation

# Build wheels for all platforms
go-to-wheel path/to/go-module

# Or with custom options
go-to-wheel ./mytool --name my-python-tool --version 1.0.0

Sources: README.md

Troubleshooting

IssueSolution
go-to-wheel: command not foundEnsure pip's Scripts directory is in PATH, or use python -m go_to_wheel
Go not foundInstall Go from go.dev and ensure it's in PATH
Python version errorUpgrade to Python 3.10 or higher
Permission deniedUse pip install --user or pipx install instead of system-wide install

Sources: spec.md

Quick Start Guide

Related topics: CLI Options Reference, Usage Examples

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Minimal Command

Continue reading this section for the full explanation and source context.

Section Build Flow

Continue reading this section for the full explanation and source context.

Section Custom Package Name

Continue reading this section for the full explanation and source context.

Related topics: CLI Options Reference, Usage Examples

Quick Start Guide

Overview

The Quick Start Guide provides the fastest path for developers to begin using go-to-wheel to compile Go CLI programs into installable Python wheels. This tool addresses a gap in the Go/Python ecosystem—there is no equivalent to Rust's maturin --bindings bin for Go. Go-to-wheel takes a Go module directory, cross-compiles it for multiple platforms, and produces properly-tagged Python wheels installable via pip or pipx. Sources: README.md:1-10

Prerequisites

Before using go-to-wheel, ensure your environment meets the following requirements:

RequirementVersionNotes
Python>= 3.10Required for installation and running the tool
Go>= 1.16Required for building Go modules Sources: spec.md:145-150
Go ModuleValid go.modThe source Go project must be a Go module

Go must be installed and available in your system PATH. No external Python dependencies are required—go-to-wheel uses only the Python standard library. Sources: go_to_wheel/__init__.py:1-15

Installation

Install go-to-wheel using pip or pipx:

pip install go-to-wheel
# or
pipx install go-to-wheel

Verify the installation:

go-to-wheel --version

Basic Usage

Minimal Command

Build wheels for all supported platforms using the simplest invocation:

go-to-wheel path/to/go-module

This command will:

  1. Cross-compile the Go binary for all default platforms
  2. Create a Python package with a thin wrapper
  3. Package everything into wheels in ./dist directory
  4. Use the directory name as the package name with version 0.1.0 Sources: README.md:35-50

Build Flow

graph TD
    A[Go Module Directory] --> B[Validate go.mod exists]
    B --> C[Cross-compile Go binary for each platform]
    C --> D[Create Python package structure]
    D --> E[Generate wheel metadata]
    E --> F[Package into .whl file]
    F --> G[Move to output directory]

Command Options

The following table documents all available command-line options:

| --ldflags FLAGS | Additional Go linker flags | None | Sources: README.md:20-35

OptionDescriptionDefault
--name NAMEPython package nameDirectory basename
--version VERSIONPackage version0.1.0
--output-dir DIRDirectory for built wheels./dist
--entry-point NAMECLI command nameSame as package name
--platforms PLATFORMSComma-separated list of targetsAll supported platforms
--go-binary PATHPath to Go binarygo
--description TEXTPackage description"Go binary packaged as Python wheel"
--license LICENSELicense identifier (e.g., MIT)None
--author AUTHORAuthor nameNone
--author-email EMAILAuthor emailNone
--url URLProject URLNone
--requires-python VERSIONPython version requirement>=3.10
--readme PATHPath to README markdown file for PyPINone
--set-version-var VARGo variable to set via -X ldflagNone

Supported Platforms

Go-to-wheel supports cross-compilation to the following target platforms:

| windows-arm64 | windows | arm64 | win_arm64 | Sources: go_to_wheel/__init__.py:20-30

Platform IdentifierGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64

Common Use Cases

Custom Package Name

Build wheels with a custom Python package name:

go-to-wheel ./mytool --name my-python-tool

Build for Specific Platforms Only

Reduce build time by targeting only required platforms:

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64

Embed Version into Go Binary

Pass the version to the Go binary at compile time using linker flags. First, add a version variable to your Go source:

var version = "dev"

func main() {
    if os.Args[1] == "--version" {
        fmt.Println(version)
    }
}

Then build with:

go-to-wheel ./mytool --version 2.0.0 --set-version-var main.version

This passes -X main.version=2.0.0 to the Go linker. The flags are appended to the default -s -w, resulting in -ldflags="-s -w -X main.version=2.0.0". Sources: README.md:55-75

Full Metadata for PyPI

Publish to PyPI with complete metadata:

go-to-wheel ./mytool \
  --name mytool-bin \
  --version 2.0.0 \
  --description "My awesome tool" \
  --license MIT \
  --author "Jane Doe" \
  --author-email "[email protected]" \
  --url "https://github.com/jane/mytool" \
  --readme README.md

Custom Linker Flags

Pass arbitrary Go linker flags for additional build-time configuration:

go-to-wheel ./mytool --version 2.0.0 \
  --ldflags "-X main.version=2.0.0 -X main.commit=abc123"

How It Works

The build process consists of four main steps:

graph LR
    A[Cross-compile Go Binary] --> B[Create Python Package]
    B --> C[Generate Metadata Files]
    C --> D[Package into Wheel]

Step 1: Cross-Compilation

For each target platform, go-to-wheel executes:

GOOS={goos} GOARCH={goarch} CGO_ENABLED=0 go build \
  -ldflags="-s -w" \
  -o {output_path} \
  {go_module_path}

Key points:

  • CGO_ENABLED=0 ensures static binaries with no libc dependency issues
  • -ldflags="-s -w" strips debug information to reduce binary size
  • Windows builds automatically receive .exe extension Sources: spec.md:100-115

Step 2: Python Package Structure

Each wheel contains a Python wrapper that executes the bundled binary:

{package_name}-{version}-py3-none-{platform_tag}.whl
├── {package_name}/
│   ├── __init__.py
│   ├── __main__.py
│   └── bin/
│       └── {binary_name}[.exe]
├── {package_name}-{version}.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── entry_points.txt

The __init__.py file contains a main() function that:

  • On Windows: uses subprocess.call() for proper signal handling
  • On Unix: uses os.execvp() to replace the Python process Sources: spec.md:60-95

Step 3: Metadata Generation

Generated METADATA follows PEP 566:

Metadata-Version: 2.1
Name: {package_name}
Version: {version}
Summary: {description}
License: {license}
Author: {author}
Author-email: {author_email}
Home-page: {url}
Requires-Python: {requires_python}

The WHEEL file follows PEP 427:

Wheel-Version: 1.0
Generator: go-to-wheel {version}
Root-Is-Purelib: false
Tag: py3-none-{platform_tag}

Step 4: Entry Points

Console script entry points are defined in entry_points.txt:

[console_scripts]
{entry_point} = {package_name}:main

The tool uses Python wrapper entry points rather than .data/scripts/ because:

  • Consistent behavior across all platforms
  • Better error messages if binary is missing
  • Future flexibility for Python-side features
  • Seamless pipx compatibility Sources: spec.md:95-105

Testing Built Wheels

Install with pip

pip install ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl

Test with uv

uv run --with ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl mytool --help

Run with pipx

pipx install ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl

After installation, the Go binary is available on your PATH through the Python wrapper. Sources: README.md:100-115

Development

To contribute or customize go-to-wheel:

# Clone the repository
git clone https://github.com/simonw/go-to-wheel
cd go-to-wheel

# Run tests
uv run pytest

Package Name Validation

Package names must follow PEP 503 naming rules:

RuleDescription
Allowed charactersLowercase letters, digits, hyphens, underscores, periods
Must start withLetter or digit
NormalizationHyphens and underscores become hyphens in wheel filename

The import name (Python package directory) follows PEP 8:

  • Hyphens are replaced with underscores
  • Example: my-tool becomes my_tool/ directory Sources: spec.md:130-140

Troubleshooting

IssueSolution
"No go.mod file found"Ensure the path points to a valid Go module directory
"Go compilation failed"Verify Go is installed and the module builds correctly standalone
Invalid package nameFollow PEP 503 naming rules (lowercase, alphanumeric with hyphens/underscores)

See Also

Source: https://github.com/simonw/go-to-wheel / Human Manual

CLI Options Reference

Related topics: Quick Start Guide, Configuration and Metadata

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Core Options

Continue reading this section for the full explanation and source context.

Section Metadata Options

Continue reading this section for the full explanation and source context.

Section Build Options

Continue reading this section for the full explanation and source context.

Related topics: Quick Start Guide, Configuration and Metadata

CLI Options Reference

Overview

The go-to-wheel CLI provides comprehensive options for cross-compiling Go binaries into Python wheels. The command follows the structure:

go-to-wheel path/to/go-folder [options]

All options are passed as space-separated arguments after the path to the Go module directory. Sources: README.md

Complete Options Reference

Core Options

OptionTypeDefaultDescription
path/to/go-folderpositionalrequiredPath to Go module directory containing go.mod
--name NAMEstringDirectory basenamePython package name for the wheel
--version VERSIONstring0.1.0Package version string
--output-dir DIRstring./distDirectory where built wheels are written
--entry-point NAMEstringSame as package nameCLI command name exposed after installation
--go-binary PATHstringgoPath to Go binary in PATH

Sources: go_to_wheel/__init__.py:52-85

Metadata Options

OptionTypeDefaultDescription
--description TEXTstring"Go binary packaged as Python wheel"Package summary for PyPI
--license LICENSEstringNoneSPDX license identifier (e.g., MIT, Apache-2.0)
--author AUTHORstringNoneAuthor name
--author-email EMAILstringNoneAuthor email address
--url URLstringNoneProject homepage URL
--requires-python VERSIONstring>=3.10Python version requirement
--readme PATHstringNonePath to README.md for PyPI long description

Sources: go_to_wheel/__init__.py:86-115

Build Options

OptionTypeDefaultDescription
--platforms PLATFORMSstringAll 8 platformsComma-separated list of target platforms
--ldflags FLAGSstringNoneAdditional Go linker flags appended to -s -w
--set-version-var VARstringNoneGo variable name for version embedding via -X

Sources: go_to_wheel/__init__.py:116-128

Supported Platforms

The following platform targets are available for cross-compilation:

Platform IdentifierGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: go_to_wheel/__init__.py:25-35

Platform Selection Syntax

Specify multiple platforms using comma-separated values:

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64,windows-amd64

Version Embedding

The --set-version-var option enables embedding the package version into the Go binary at compile time. This requires a matching variable in the Go source:

var version = "dev"

func main() {
    if len(os.Args) > 1 && os.Args[1] == "--version" {
        fmt.Println(version)
    }
}

Build command with version embedding:

go-to-wheel ./mytool --version 2.0.0 --set-version-var main.version

This passes -X main.version=2.0.0 to the Go linker. Sources: README.md

Advanced Ldflags

The --ldflags option appends custom linker flags to the default -s -w flags (which strip debug info):

go-to-wheel ./mytool --ldflags "-X main.commit=abc123 -X main.date=2024-01-15"

The combined ldflags become: -s -w -X main.commit=abc123 -X main.date=2024-01-15

Usage Examples

Minimal Build

go-to-wheel ./mytool

Produces wheels for all 8 platforms using default settings. Sources: README.md

Custom Package Name

go-to-wheel ./mytool --name my-python-tool

Creates my-python-tool-0.1.0-py3-none-*.whl files.

PyPI-Ready Build

go-to-wheel ./mytool \
  --name mytool-bin \
  --version 2.0.0 \
  --description "My awesome CLI tool" \
  --license MIT \
  --author "Jane Doe" \
  --author-email "[email protected]" \
  --url "https://github.com/jane/mytool" \
  --readme README.md

Platform-Specific Build

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64

With Version Embedding

go-to-wheel ./mytool --version 1.2.3 --set-version-var main.version

Option Processing Flow

graph TD
    A[Parse CLI Arguments] --> B[Validate go_dir exists]
    B --> C{Check go.mod}
    C -->|Valid| D[Set defaults]
    C -->|Invalid| E[Exit with error]
    D --> F[Parse platforms list]
    F --> G[Build ldflags]
    G --> H[Cross-compile for each platform]
    H --> I[Generate Python wheel]
    I --> J[Move to output-dir]
    J --> K[Print summary]

Default Values Behavior

OptionDefault SourceOverride Mechanism
Package namego_path.name (directory basename)--name
Version"0.1.0"--version
Entry pointSame as package name--entry-point
PlatformsDEFAULT_PLATFORMS list (all 8)--platforms
Go binary"go" from PATH--go-binary
Description"Go binary packaged as Python wheel"--description
Requires Python">=3.10"--requires-python

Sources: go_to_wheel/__init__.py:175-190

Error Handling

The CLI validates inputs and exits with descriptive errors:

Error ConditionExit Behavior
Go directory not foundFileNotFoundError: Go directory not found: <path>
No go.mod fileValueError: Not a Go module: <path>
Go binary not foundFileNotFoundError from subprocess
Compilation failureRuntimeError: Go compilation failed for <os>/<arch>
No wheels builtError: No wheels were built

Return Codes

CodeMeaning
0Success - wheels built and written to output directory
1Error occurred (invalid input, compilation failure, etc.)

Sources: go_to_wheel/__init__.py:130-155

Sources: go_to_wheel/__init__.py:52-85

Usage Examples

Related topics: Quick Start Guide, Wheel Generation Process

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Option Reference Table

Continue reading this section for the full explanation and source context.

Section Build for All Platforms

Continue reading this section for the full explanation and source context.

Section Build for Specific Platforms

Continue reading this section for the full explanation and source context.

Related topics: Quick Start Guide, Wheel Generation Process

Usage Examples

This page provides comprehensive usage examples for go-to-wheel, demonstrating how to compile Go CLI programs into distributable Python wheels. The examples progress from basic usage to advanced configurations, covering all available command-line options and common use cases.

Overview

go-to-wheel transforms Go binaries into Python packages that can be installed via pip or pipx. The tool handles cross-compilation for multiple platforms, generates proper Python wheel metadata, and creates installable packages with executable entry points. All examples assume Go is installed and available in the system PATH.

Basic Usage

The simplest way to build wheels from a Go module is to pass the module directory path:

go-to-wheel ./mytool

This command:

  1. Locates the Go module in ./mytool (requires go.mod file)
  2. Cross-compiles the binary for all supported platforms
  3. Creates wheels in ./dist directory
  4. Uses the directory name as the package name
  5. Defaults to version 0.1.0

The resulting wheels follow the naming convention {name}-{version}-py3-none-{platform_tag}.whl.

Command Line Options

Option Reference Table

OptionDescriptionDefault Value
--name NAMEPython package nameDirectory basename
--version VERSIONPackage version0.1.0
--output-dir DIRDirectory for built wheels./dist
--entry-point NAMECLI command nameSame as package name
--platforms PLATFORMSComma-separated list of targetsAll supported platforms
--go-binary PATHPath to Go binarygo
--description TEXTPackage description"Go binary packaged as Python wheel"
--license LICENSELicense identifierNone
--author AUTHORAuthor nameNone
--author-email EMAILAuthor emailNone
--url URLProject URLNone
--requires-python VERSIONPython version requirement>=3.10
--readme PATHPath to README markdown fileNone
--set-version-var VARGo variable for --version valueNone
--ldflags FLAGSAdditional Go linker flagsNone

Sources: go_to_wheel/__init__.py:1-100

Platform Selection

Build for All Platforms

By default, go-to-wheel builds wheels for all supported platforms:

PlatformWheel Tag
linux-amd64manylinux_2_17_x86_64
linux-arm64manylinux_2_17_aarch64
linux-amd64-muslmusllinux_1_2_x86_64
linux-arm64-muslmusllinux_1_2_aarch64
darwin-amd64macosx_10_9_x86_64
darwin-arm64macosx_11_0_arm64
windows-amd64win_amd64
windows-arm64win_arm64

Sources: README.md:1-50

Build for Specific Platforms

To build only for specific platforms, use the --platforms option with a comma-separated list:

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64

This produces wheels for only Linux amd64 and macOS ARM64, reducing build time when you don't need all platform variants.

Using a Custom Go Binary

If Go is not in your PATH or you need a specific version:

go-to-wheel ./mytool --go-binary /usr/local/go/bin/go

Package Naming

Custom Package Name

Override the default package name derived from the directory:

go-to-wheel ./mytool --name my-python-tool

This creates wheels named my-python-tool-{version}-py3-none-{platform_tag}.whl.

Custom Entry Point

The CLI command name can differ from the package name:

go-to-wheel ./mytool --name my-tool-bin --entry-point mytool

This creates a package named my-tool-bin but installs the command as mytool.

Version Management

Setting Package Version

go-to-wheel ./mytool --version 2.0.0

Embedding Version in Go Binary

To embed the version into the Go binary at compile time, define a version variable in your Go source:

var version = "dev"

func main() {
    if os.Args[1] == "--version" {
        fmt.Println(version)
    }
}

Then use --set-version-var to pass the version via linker flags:

go-to-wheel ./mytool --version 2.0.0 --set-version-var main.version

This passes -X main.version=2.0.0 to the Go linker. The combined linker flags become -s -w -X main.version=2.0.0.

Sources: README.md:50-80

Custom Linker Flags

Using ldflags

Pass arbitrary Go linker flags with --ldflags:

go-to-wheel ./mytool --version 2.0.0 \
  --ldflags "-X main.version=2.0.0 -X main.commit=abc123"

Flags are appended to the default -s -w, so the full linker invocation becomes:

-ldflags="-s -w -X main.version=2.0.0 -X main.commit=abc123"

The -s flag strips symbol table, and -w removes DWARF debugging information, reducing binary size.

Sources: spec.md:50-80

Output Management

Custom Output Directory

go-to-wheel ./mytool --output-dir ./wheels

Installing Built Wheels

After building, install wheels with pip:

pip install ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl

Or test directly with uv:

uv run --with ./dist/mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whl mytool --help

Complete Examples

Basic Distribution

For internal distribution without PyPI publishing:

go-to-wheel ./mytool

PyPI-Ready Distribution

Full metadata configuration for PyPI publishing:

go-to-wheel ./mytool \
  --name mytool-bin \
  --version 2.0.0 \
  --description "My awesome tool" \
  --license MIT \
  --author "Jane Doe" \
  --author-email "[email protected]" \
  --url "https://github.com/jane/mytool" \
  --readme README.md

This generates wheels with complete metadata suitable for publishing to PyPI. The --readme option sets the long description from your README file.

Sources: README.md:80-120

Development Workflow

Typical development workflow:

# Build all wheels
go-to-wheel ./mytool --output-dir ./dist

# Test specific wheel
uv run --with ./dist/mytool-0.1.0-py3-none-manylinux_2_17_x86_64.whl mytool --help

# Install locally for testing
pip install ./dist/mytool-0.1.0-py3-none-manylinux_2_17_x86_64.whl

# Uninstall after testing
pip uninstall mytool

Build Process Flow

graph TD
    A[go-to-wheel command] --> B[Validate Go directory]
    B --> C[Check go.mod exists]
    C --> D{For each platform}
    D --> E[Cross-compile with GOOS/GOARCH]
    E --> F[Create Python package structure]
    F --> G[Generate __init__.py and __main__.py]
    G --> H[Generate METADATA and WHEEL files]
    H --> I[Calculate RECORD with SHA256]
    I --> J[Zip into wheel file]
    J --> K{More platforms?}
    K -->|Yes| D
    K -->|No| L[Move wheels to output dir]
    L --> M[Print summary]

The build process uses CGO_ENABLED=0 for static binaries, ensuring compatibility across different Linux distributions without libc dependencies.

Sources: spec.md:30-60

Error Handling

Common errors and solutions:

ErrorCauseSolution
Go directory not foundInvalid pathVerify the directory exists
Not a Go moduleMissing go.modEnsure directory contains go.mod
Go compilation failedBuild error in Go codeCheck Go source for errors
No wheels were builtPlatform validation failedVerify platform names are correct

Sources: go_to_wheel/__init__.py:150-200

Quick Reference

TaskCommand
Build all wheelsgo-to-wheel ./mytool
Custom namego-to-wheel ./mytool --name my-package
Specific versiongo-to-wheel ./mytool --version 1.2.3
Specific platformsgo-to-wheel ./mytool --platforms linux-amd64,darwin-arm64
With metadatago-to-wheel ./mytool --author "Name" --license MIT --readme README.md
Embed versiongo-to-wheel ./mytool --version 1.0 --set-version-var main.version

Sources: go_to_wheel/__init__.py:1-100

System Architecture

Related topics: Wheel Generation Process, Supported Platforms

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Component Responsibilities

Continue reading this section for the full explanation and source context.

Section Platform Mapping Table

Continue reading this section for the full explanation and source context.

Section Cross-Compilation Details

Continue reading this section for the full explanation and source context.

Related topics: Wheel Generation Process, Supported Platforms

System Architecture

Overview

go-to-wheel is a Python CLI tool that bridges the Go and Python packaging ecosystems. It takes a Go module directory, cross-compiles it for multiple target platforms, and packages each binary as a PEP 427-compliant Python wheel with executable entry points. This enables Go binaries to be distributed and installed via pip or pipx.

Sources: spec.md

Architecture Components

The system consists of four primary components working in sequence:

graph TD
    A[Go Module Input] --> B[Input Validation]
    B --> C[Cross-Compilation Engine]
    C --> D[Wheel Builder]
    D --> E[Output Wheels]
    
    B --> B1[Verify go.mod exists]
    B --> B2[Validate package name]
    C --> C1[GOOS/GOARCH env vars]
    C --> C2[CGO_ENABLED=0]
    D --> D1[Generate METADATA]
    D --> D2[Generate WHEEL]
    D --> D3[Generate RECORD]
    D --> D4[Create zip archive]

Component Responsibilities

ComponentPurposeKey Functions
CLI ParserParse command-line argumentsargparse, argument validation
Input ValidatorVerify Go module structurePath existence, go.mod detection
Cross-CompilerBuild binaries for target platformssubprocess.run(), env var management
Wheel BuilderCreate PEP 427 compliant wheelszipfile, metadata generation

Sources: go_to_wheel/__init__.py:1-50

Platform Mapping System

The platform mapping system translates human-readable platform names into Go build environment variables and Python wheel tags.

graph LR
    P1["linux-amd64"] --> PM1["PLATFORM_MAPPINGS"]
    P2["darwin-arm64"] --> PM1
    P3["windows-amd64"] --> PM1
    
    PM1 --> G["GOOS/GOARCH"]
    PM1 --> W["Wheel Tag"]
    
    G --> G1["linux/amd64"]
    W --> W1["manylinux_2_17_x86_64"]

Platform Mapping Table

Platform NameGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: go_to_wheel/__init__.py:23-32

Build Process Workflow

flowchart TD
    START[Start build_wheels] --> V1{Go directory exists?}
    V1 -->|No| ERROR1[FileNotFoundError]
    V1 -->|Yes| V2{go.mod exists?}
    V2 -->|No| ERROR2[ValueError]
    V2 -->|Yes| PARSE[Parse arguments]
    
    PARSE --> SETUP[Set defaults<br/>name, entry_point,<br/>platforms]
    
    SETUP --> FOR_LOOP[For each platform]
    FOR_LOOP --> CROSS[cross_compile_go]
    
    CROSS --> BUILD[Build wheel structure]
    BUILD --> GEN_INIT[Generate __init__.py]
    BUILD --> GEN_MAIN[Generate __main__.py]
    BUILD --> GEN_META[Generate METADATA]
    BUILD --> GEN_WHEEL[Generate WHEEL]
    BUILD --> GEN_RECORD[Generate RECORD]
    BUILD --> GEN_ENTRY[Generate entry_points.txt]
    
    GEN_INIT --> ZIP[Create wheel.zip]
    GEN_MAIN --> ZIP
    GEN_META --> ZIP
    GEN_WHEEL --> ZIP
    GEN_RECORD --> ZIP
    GEN_ENTRY --> ZIP
    
    ZIP --> NEXT{More platforms?}
    NEXT -->|Yes| FOR_LOOP
    NEXT -->|No| OUTPUT[Move wheels to output_dir]
    OUTPUT --> DONE[Return wheel paths]

Cross-Compilation Details

The cross-compilation step uses Go's built-in cross-compilation support:

GOOS={goos} GOARCH={goarch} CGO_ENABLED=0 go build \
  -ldflags="-s -w" \
  -o {output_path} \
  {go_module_path}

Key compilation flags:

  • CGO_ENABLED=0: Disables C bindings for static binaries
  • -ldflags="-s -w": Strips debug info and symbol tables
  • .exe extension added automatically on Windows

Sources: spec.md

Wheel Structure

Each generated wheel follows the PEP 427 standard structure:

{package_name}-{version}-py3-none-{platform_tag}.whl
├── {package_name}/
│   ├── __init__.py
│   ├── __main__.py
│   └── bin/
│       └── {binary_name}[.exe]
├── {package_name}-{version}.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── entry_points.txt

Sources: spec.md

Generated File Contents

#### __init__.py

The wrapper module that locates and executes the bundled binary:

"""Go binary packaged as Python wheel."""

import os
import stat
import subprocess
import sys

__version__ = "{version}"

def get_binary_path():
    """Return the path to the bundled binary."""
    binary = os.path.join(os.path.dirname(__file__), "bin", "{binary_name}")

    # Ensure binary is executable on Unix
    if sys.platform != "win32":
        current_mode = os.stat(binary).st_mode
        if not (current_mode & stat.S_IXUSR):
            os.chmod(binary, current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
    return binary

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

Sources: go_to_wheel/__init__.py:75-100

#### METADATA (PEP 566)

Metadata-Version: 2.1
Name: {package_name}
Version: {version}
Summary: {description}
License: {license}
Author: {author}
Author-email: {author_email}
Home-page: {url}
Requires-Python: {requires_python}

#### WHEEL (PEP 427)

Wheel-Version: 1.0
Generator: go-to-wheel {go_to_wheel_version}
Root-Is-Purelib: false
Tag: py3-none-{platform_tag}

#### RECORD

CSV format with columns: path,hash,size

#### entry_points.txt

[console_scripts]
{entry_point} = {package_name}:main

Sources: spec.md

Command-Line Interface

Options Table

OptionDescriptionDefault
--name NAMEPython package nameDirectory basename
--version VERSIONPackage version0.1.0
--output-dir DIRDirectory for built wheels./dist
--entry-point NAMECLI command nameSame as package name
--platforms PLATFORMSComma-separated list of targetsAll 8 platforms
--go-binary PATHPath to Go binarygo
--description TEXTPackage description"Go binary packaged as Python wheel"
--license LICENSELicense identifierNone
--author AUTHORAuthor nameNone
--author-email EMAILAuthor emailNone
--url URLProject URLNone
--requires-python VERSIONPython version requirement>=3.10
--readme PATHPath to README markdown fileNone
--set-version-var VARGo variable for version via -XNone
--ldflags FLAGSAdditional Go linker flagsNone

Sources: go_to_wheel/__init__.py:50-130

Data Flow Diagram

flowchart LR
    subgraph Input
        A[Go Module Directory]
        B[CLI Arguments]
    end
    
    subgraph Processing
        C[Input Validation]
        D[Cross-Compilation]
        E[Metadata Generation]
        F[Zip Packaging]
    end
    
    subgraph Output
        G[Python Wheel Files]
        H[dist/ Directory]
    end
    
    A --> C
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G
    G --> H

Key Function Signatures

`build_wheels()`

def build_wheels(
    go_dir: str,
    *,
    name: str | None = None,
    version: str = "0.1.0",
    output_dir: str = "./dist",
    entry_point: str | None = None,
    platforms: list[str] | None = None,
    go_binary: str = "go",
    description: str = "Go binary packaged as Python wheel",
    requires_python: str = ">=3.10",
    author: str | None = None,
    author_email: str | None = None,
    license_: str | None = None,
    url: str | None = None,
    readme: str | None = None,
    ldflags: str | None = None,
    set_version_var: str | None = None,
) -> list[str]:
    """Build Python wheels from a Go module."""

Sources: go_to_wheel/__init__.py:150-200

`cross_compile_go()`

def cross_compile_go(
    go_dir: Path,
    output_path: Path,
    goos: str,
    goarch: str,
    go_binary: str = "go",
    ldflags: str | None = None,
) -> None:
    """Cross-compile Go binary for target platform."""

Sources: go_to_wheel/__init__.py:65-92

Dependency Architecture

graph TD
    A[go-to-wheel] --> B[Python Standard Library]
    
    B --> B1[argparse]
    B --> B2[zipfile]
    B --> B3[subprocess]
    B --> B4[pathlib]
    B --> B5[hashlib]
    B --> B6[csv]
    
    A --> C[External Dependency]
    C --> C1[Go Toolchain]

Python Dependencies

The tool uses only Python standard library modules, requiring no external Python dependencies:

  • argparse: CLI argument parsing
  • zipfile: Wheel archive creation
  • subprocess: Go compilation invocation
  • pathlib: Path manipulation
  • hashlib: SHA256 hashing for RECORD
  • csv: RECORD file generation
  • tempfile: Temporary build directory
  • shutil: File operations

External Dependencies

  • Go >= 1.16: Required for go build and cross-compilation

Sources: spec.md

Version Information

ItemValue
Project Version0.1.0
Python Requirement>=3.10
Go Requirement>=1.16
LicenseApache 2.0

Sources: go_to_wheel/__init__.py:10

Installation Methods

graph TD
    A[User] --> B{Installation Method}
    
    B --> C[pip install]
    B --> D[pipx install]
    
    C --> E[System Python]
    D --> F[Isolated environment<br/>with PATH access]
    
    E --> G[Requires Go in PATH]
    F --> G

Sources: README.md

Sources: spec.md

Supported Platforms

Related topics: System Architecture, Wheel Generation Process

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Building All Platforms (Default)

Continue reading this section for the full explanation and source context.

Section Building Specific Platforms

Continue reading this section for the full explanation and source context.

Section glibc-based (manylinux)

Continue reading this section for the full explanation and source context.

Related topics: System Architecture, Wheel Generation Process

Supported Platforms

go-to-wheel provides comprehensive cross-platform support for distributing Go CLI binaries as Python wheels. The tool automatically generates wheels for all supported platforms with correct PEP 427 tags, enabling seamless installation via pip or pipx.

Platform Architecture

go-to-wheel maps Go's GOOS/GOARCH environment variables to Python wheel platform tags. The mapping system allows for precise targeting of specific architectures while maintaining compatibility with the Python packaging ecosystem.

graph TD
    A[Go Source Code] --> B[go-to-wheel build]
    B --> C{Platform Selection}
    C -->|Default| D[All 8 Platforms]
    C -->|Custom| E[User-specified subset]
    
    D --> F[Cross-compile with GOOS/GOARCH]
    E --> F
    
    F --> G[Generate wheel with platform tag]
    G --> H[Installable via pip/pipx]

Sources: go_to_wheel/__init__.py:9-27

Default Supported Platforms

The following 8 platform targets are built by default when no --platforms flag is specified:

Platform IdentifierGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: go_to_wheel/__init__.py:9-27

Platform Selection Options

Building All Platforms (Default)

Without specifying platforms, all 8 targets are built:

go-to-wheel ./mytool

Building Specific Platforms

Use the --platforms flag with a comma-separated list:

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64

Only Linux amd64 and macOS ARM64 wheels will be generated.

Sources: README.md

Linux Variants

glibc-based (manylinux)

The linux-amd64 and linux-arm64 targets produce binaries linked against glibc, using the manylinux_2_17 container tag. These wheels are compatible with most modern Linux distributions including:

  • Ubuntu 18.04+
  • Debian 10+
  • Fedora 30+
  • RHEL/CentOS 8+
  • Amazon Linux 2

musl-based (musllinux)

The linux-amd64-musl and linux-arm64-musl targets produce static binaries linked against musl libc. These wheels target:

  • Alpine Linux (all versions)
  • OpenWrt
  • Other musl-based distributions

Musl builds are compiled with CGO_ENABLED=0 to ensure fully static linking, eliminating any libc dependency issues.

Sources: spec.md

macOS Support

Universal2 Consideration

While go-to-wheel does not natively generate darwin-universal2 wheels that combine both architectures, the tool can build both darwin-amd64 and darwin-arm64 separately. Users can combine them using Apple's lipo tool if a single universal binary is required:

lipo -create mytool-darwin-amd64 mytool-darwin-arm64 -output mytool-darwin-universal

Sources: spec.md

macOS Version Compatibility

Wheel TagMinimum macOS VersionArchitecture
macosx_10_9_x86_64macOS 10.9 (Mavericks)Intel
macosx_11_0_arm64macOS 11.0 (Big Sur)Apple Silicon

The x86_64 tag maintains backwards compatibility to macOS 10.9, while the ARM64 tag starts from macOS 11.0 due to Apple Silicon hardware requirements.

Windows Support

Windows builds are cross-compiled from Linux/macOS hosts using CGO_ENABLED=0. The tool automatically appends the .exe extension to the binary within the wheel.

Wheel TagArchitectureNotes
win_amd64x86_6464-bit Windows
win_arm64ARM64Windows on ARM devices

Build Process

The platform-specific build workflow demonstrates how go-to-wheel handles cross-compilation:

graph LR
    A[Source Host] -->|GOOS=linux GOARCH=amd64| B[Linux amd64 binary]
    A -->|GOOS=linux GOARCH=arm64| C[Linux arm64 binary]
    A -->|GOOS=darwin GOARCH=amd64| D[macOS amd64 binary]
    A -->|GOOS=darwin GOARCH=arm64| E[macOS arm64 binary]
    A -->|GOOS=windows GOARCH=amd64| F[Windows amd64 binary]
    
    B --> G[Wheel: manylinux_2_17_x86_64]
    C --> H[Wheel: manylinux_2_17_aarch64]
    D --> I[Wheel: macosx_10_9_x86_64]
    E --> J[Wheel: macosx_11_0_arm64]
    F --> K[Wheel: win_amd64]
    
    style A fill:#f9f,color:#000
    style G fill:#9f9,color:#000
    style H fill:#9f9,color:#000
    style I fill:#9f9,color:#000
    style J fill:#9f9,color:#000
    style K fill:#9f9,color:#000

Each build uses the following environment configuration:

env["GOOS"] = goos
env["GOARCH"] = goarch
env["CGO_ENABLED"] = "0"

Sources: go_to_wheel/__init__.py:129-136

Wheel Installation Compatibility

The wheel filename format ensures pip automatically selects the correct wheel for the target platform:

{package_name}-{version}-py3-none-{platform_tag}.whl

Example wheel names:

Wheel FilenamePlatform
mytool-1.0.0-py3-none-manylinux_2_17_x86_64.whlLinux x86_64 (glibc)
mytool-1.0.0-py3-none-musllinux_1_2_aarch64.whlLinux ARM64 (musl)
mytool-1.0.0-py3-none-macosx_11_0_arm64.whlmacOS Apple Silicon
mytool-1.0.0-py3-none-win_amd64.whlWindows x86_64

Sources: spec.md

Requirements

Building wheels requires:

ComponentRequirement
Go>= 1.16 (for go mod support)
Python>= 3.10
Python dependenciesNone (stdlib only)

The build host does not need to match the target platform due to Go's native cross-compilation support.

Platform Detection for Binary Execution

The generated __init__.py wrapper uses sys.platform to handle platform-specific behavior:

def main():
    """Execute the bundled binary."""
    binary = get_binary_path()
    if sys.platform == "win32":
        # On Windows, use subprocess to properly handle signals
        sys.exit(subprocess.call([binary] + sys.argv[1:]))
    else:
        # On Unix, exec replaces the process
        os.execvp(binary, [binary] + sys.argv[1:])

This ensures consistent behavior across all supported platforms while respecting OS-specific process management conventions.

Sources: go_to_wheel/__init__.py:158-170

Sources: go_to_wheel/__init__.py:9-27

Wheel Generation Process

Related topics: System Architecture, Supported Platforms

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Supported Platforms

Continue reading this section for the full explanation and source context.

Section Platform Mapping Structure

Continue reading this section for the full explanation and source context.

Section High-Level Workflow

Continue reading this section for the full explanation and source context.

Related topics: System Architecture, Supported Platforms

Wheel Generation Process

Overview

The Wheel Generation Process is the core mechanism of go-to-wheel that transforms a Go CLI program into a distributable Python wheel package. This process handles cross-compilation, binary packaging, and wheel metadata generation for multiple target platforms in a single operation.

The wheel generation workflow accepts a Go module directory, validates the input, compiles static binaries for each target platform, creates a Python package wrapper, and packages everything into properly tagged wheel files following PEP 427 and PEP 376 standards.

Sources: spec.md:1-25

Platform Configuration

Supported Platforms

The tool supports eight target platforms by default, covering major operating systems and architectures:

Platform KeyGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: go_to_wheel/__init__.py:15-29

Platform Mapping Structure

The PLATFORM_MAPPINGS dictionary maps platform keys to a tuple containing the Go operating system, Go architecture, and the corresponding Python wheel platform tag:

PLATFORM_MAPPINGS: dict[str, tuple[str, str, str]] = {
    "linux-amd64": ("linux", "amd64", "manylinux_2_17_x86_64"),
    # ... additional platforms
}

The DEFAULT_PLATFORMS list defines which platforms are built when no specific platform is specified:

DEFAULT_PLATFORMS = [
    "linux-amd64",
    "linux-arm64",
    "linux-amd64-musl",
    "linux-arm64-musl",
    "darwin-amd64",
    "darwin-arm64",
    "windows-amd64",
    "windows-arm64",
]

Sources: go_to_wheel/__init__.py:31-37

Build Process Architecture

High-Level Workflow

graph TD
    A[Start: go-to-wheel Command] --> B[Validate Go Directory]
    B --> C{Valid?}
    C -->|No| D[Error: Directory not found or no go.mod]
    C -->|Yes| E[Parse Options and Set Defaults]
    E --> F{Platforms specified?}
    F -->|Yes| G[Use Specified Platforms]
    F -->|No| H[Use DEFAULT_PLATFORMS]
    G --> I[For Each Platform]
    H --> I
    I --> J[Cross-Compile Go Binary]
    J --> K[Create Package Directory Structure]
    K --> L[Generate Python Wrapper Files]
    L --> M[Generate Metadata Files]
    M --> N[Create Wheel ZIP Archive]
    N --> O{More Platforms?}
    O -->|Yes| I
    O -->|No| P[Move Wheels to Output Directory]
    P --> Q[Print Summary]

Main Entry Point

The build_wheels() function serves as the primary entry point for the wheel generation process. It accepts the following parameters:

ParameterTypeDefaultDescription
go_dirstrRequiredPath to Go module directory
name`str \None`NonePython package name (defaults to directory basename)
versionstr"0.1.0"Package version
output_dirstr"./dist"Directory for built wheels
entry_point`str \None`NoneCLI command name (defaults to package name)
platforms`list[str] \None`NoneTarget platforms (defaults to all)
go_binarystr"go"Path to Go binary
descriptionstr"Go binary packaged as Python wheel"Package description
requires_pythonstr">=3.10"Python version requirement
author`str \None`NoneAuthor name
author_email`str \None`NoneAuthor email
license_`str \None`NoneLicense identifier
url`str \None`NoneProject URL
readme`str \None`NonePath to README markdown file
ldflags`str \None`NoneAdditional Go linker flags
set_version_var`str \None`NoneGo variable to set via -X ldflag

Sources: go_to_wheel/__init__.py:136-168

Step-by-Step Generation Process

Step 1: Input Validation

The process begins with comprehensive validation of the input parameters:

  1. Directory Existence: Verify the Go directory exists using Path.resolve()
  2. Go Module Verification: Check for go.mod file to confirm valid Go module
  3. README File Check: If --readme is provided, verify the file exists and read its content
  4. Default Value Assignment: Set defaults for name and entry_point from directory name
go_path = Path(go_dir).resolve()

if not go_path.exists():
    raise FileNotFoundError(f"Go directory not found: {go_dir}")

if not (go_path / "go.mod").exists():
    raise ValueError(f"Not a Go module: {go_dir} (no go.mod file found)")

Sources: go_to_wheel/__init__.py:179-186

Step 2: Cross-Compilation

Each Go binary is compiled for its target platform using environment variables:

graph LR
    A[Platform: linux-amd64] --> B["GOOS=linux<br/>GOARCH=amd64<br/>CGO_ENABLED=0"]
    B --> C["go build<br/>-ldflags='-s -w'<br/>-o output"]
    C --> D[Static Binary]
    
    E[Platform: windows-amd64] --> F["GOOS=windows<br/>GOARCH=amd64<br/>CGO_ENABLED=0"]
    F --> G["go build<br/>-ldflags='-s -w'<br/>-o output.exe"]
    G --> H[Static Binary .exe]

Compilation Environment Variables:

VariableValuePurpose
GOOSPlatform's OSTarget operating system
GOARCHPlatform's architectureTarget CPU architecture
CGO_ENABLED0Disable CGO for static binaries

Linker Flags:

The default linker flags -s -w strip debug information and symbol tables to reduce binary size. Additional flags can be appended via the --ldflags option:

GOOS={goos} GOARCH={goarch} CGO_ENABLED=0 go build \
  -ldflags="-s -w {additional_flags}" \
  -o {output_path} \
  {go_module_path}

Sources: spec.md:145-156

Step 3: Wheel Package Structure Creation

The wheel contains a specific directory structure following PEP 427:

{package_name}-{version}-py3-none-{platform_tag}.whl
├── {package_name}/
│   ├── __init__.py
│   ├── __main__.py
│   └── bin/
│       └── {binary_name}[.exe]
├── {package_name}-{version}.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── entry_points.txt

Sources: spec.md:40-57

Step 4: Python Wrapper Generation

The generated __init__.py provides a thin wrapper that executes the bundled binary:

"""Go binary packaged as Python wheel."""

import os
import sys
import subprocess

__version__ = "{version}"

def get_binary_path():
    """Return the path to the bundled binary."""
    return os.path.join(os.path.dirname(__file__), "bin", "{binary_name}")

def main():
    """Execute the bundled binary."""
    binary = get_binary_path()
    if sys.platform == "win32":
        # On Windows, use subprocess to properly handle signals
        sys.exit(subprocess.call([binary] + sys.argv[1:]))
    else:
        # On Unix, exec replaces the process
        os.execvp(binary, [binary] + sys.argv[1:])

The __main__.py serves as the entry point bridge:

from . import main
main()

Sources: spec.md:63-87

Step 5: Metadata File Generation

#### METADATA (PEP 566)

Generated dynamically with package metadata:

Metadata-Version: 2.1
Name: {normalized_name}
Version: {version}
Summary: {description}
License: {license}
Author: {author}
Author-email: {author_email}
Home-page: {url}
Requires-Python: {requires_python}

#### WHEEL (PEP 427)

Wheel-Version: 1.0
Generator: go-to-wheel {version}
Root-Is-Purelib: false
Tag: py3-none-{platform_tag}

#### entry_points.txt

[console_scripts]
{entry_point} = {package_name}:main

#### RECORD (PEP 376)

CSV format tracking all files with SHA256 hashes:

{package_name}/__init__.py,sha256={hash},{size}
{package_name}/__main__.py,sha256={hash},{size}
{package_name}/bin/{binary_name},sha256={hash},{size}
{package_name}-{version}.dist-info/METADATA,sha256={hash},{size}
{package_name}-{version}.dist-info/WHEEL,sha256={hash},{size}
{package_name}-{version}.dist-info/entry_points.txt,sha256={hash},{size}
{package_name}-{version}.dist-info/RECORD,,

Sources: spec.md:162-183

Step 6: Wheel Archive Creation

The wheel is created as a ZIP archive with specific handling:

with zipfile.ZipFile(wheel_path, "w", zipfile.ZIP_DEFLATED) as whl:
    for file_path, content in files.items():
        # Set executable permission for binary
        if "/bin/" in file_path:
            info = zipfile.ZipInfo(file_path)
            # Set Unix permissions: rwxr-xr-x (0755)
            info.external_attr = (
                stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | 
                stat.S_IROTH | stat.S_IXOTH
            ) << 16
            whl.writestr(info, content)
        else:
            whl.writestr(file_path, content)

Binary files receive executable permissions (0755) via the ZIP external attributes, ensuring they can be executed after installation.

Sources: go_to_wheel/__init__.py:110-123

Wheel Naming Convention

Wheels follow PEP 427 naming with format:

{name}-{version}-py3-none-{platform_tag}.whl

Examples:

PackageVersionPlatformFull Wheel Name
myapp1.0.0linux-amd64myapp-1.0.0-py3-none-manylinux_2_17_x86_64.whl
myapp1.0.0darwin-arm64myapp-1.0.0-py3-none-macosx_11_0_arm64.whl
myapp1.0.0windows-amd64myapp-1.0.0-py3-none-win_amd64.whl

Package names are normalized per PEP 503: hyphens are used in wheel filenames, while the import name (directory) uses underscores.

Sources: spec.md:207-216

Linker Flags Integration

Version Variable Setting

The --set-version-var option embeds the package version into a Go variable at compile time:

go-to-wheel ./mytool --version 2.0.0 --set-version-var main.version

This passes -X main.version=2.0.0 to the Go linker. The combined ldflags are constructed as:

combined_ldflags_parts: list[str] = []
if set_version_var:
    combined_ldflags_parts.append(f"-X {set_version_var}={version}")
if ldflags:
    combined_ldflags_parts.append(ldflags)

combined_ldflags = " ".join(combined_ldflags_parts)

The final linker invocation becomes: -ldflags="-s -w -X main.version=2.0.0"

Sources: go_to_wheel/__init__.py:196-203

Custom ldflags

Additional arbitrary linker flags can be passed with --ldflags:

go-to-wheel ./mytool --ldflags "-X main.version=2.0.0 -X main.commit=abc123"

These flags are appended to the default -s -w, so the full invocation becomes -ldflags="-s -w -X main.version=2.0.0 -X main.commit=abc123".

Sources: README.md:48-51

Error Handling

Error ConditionHandling
Directory not foundFileNotFoundError with message
No go.mod fileValueError indicating invalid Go module
README file not foundFileNotFoundError for README path
Go binary not foundFileNotFoundError from subprocess
Build failureException propagates with Go error output
No wheels builtPrints error and returns exit code 1

The CLI entry point catches FileNotFoundError and ValueError exceptions, printing them to stderr and returning exit code 1:

try:
    wheels = build_wheels(...)
except (FileNotFoundError, ValueError) as e:
    print(f"Error: {e}", file=sys.stderr)
    return 1

Sources: go_to_wheel/__init__.py:92-95

Output Example

Successful wheel generation produces output like:

go-to-wheel v0.1.0
Building from ./myapp

  ✓ myapp-1.0.0-py3-none-manylinux_2_17_x86_64.whl
  ✓ myapp-1.0.0-py3-none-manylinux_2_17_aarch64.whl
  ✓ myapp-1.0.0-py3-none-musllinux_1_2_x86_64.whl
  ✓ myapp-1.0.0-py3-none-musllinux_1_2_aarch64.whl
  ✓ myapp-1.0.0-py3-none-macosx_10_9_x86_64.whl
  ✓ myapp-1.0.0-py3-none-macosx_11_0_arm64.whl
  ✓ myapp-1.0.0-py3-none-win_amd64.whl
  ✓ myapp-1.0.0-py3-none-win_arm64.whl

Done! Built 8 wheels in ./dist

Sources: spec.md:225-240

ComponentPurpose
build_wheels()Main orchestrator function
build_single_wheel()Creates wheel for one platform
compile_go_binary()Executes Go cross-compilation
generate_metadata()Creates METADATA content
generate_wheel_file()Creates WHEEL file content
generate_record()Creates RECORD with hashes
generate_entry_points()Creates entry_points.txt

These functions work together to transform a Go module into a distributable Python wheel package that can be installed via pip or pipx.

Sources: go_to_wheel/__init__.py:96-135

See Also

Sources: spec.md:1-25

Development Guide

Related topics: Installation, Configuration and Metadata

Section Related Pages

Continue reading this section for the full explanation and source context.

Section 1. Clone the Repository

Continue reading this section for the full explanation and source context.

Section 2. Install Dependencies

Continue reading this section for the full explanation and source context.

Section 3. Verify Installation

Continue reading this section for the full explanation and source context.

Related topics: Installation, Configuration and Metadata

Development Guide

Overview

This guide covers everything you need to know to develop, test, and contribute to go-to-wheel. The project is a Python CLI tool that compiles Go programs into Python wheels, enabling distribution of Go binaries via PyPI and pipx. Sources: README.md:1-15

The tool requires no external Python dependencies—only Python's standard library is used—making it lightweight and easy to maintain. Sources: spec.md:95-97

Prerequisites

Before setting up a development environment, ensure you have the following installed:

RequirementMinimum VersionPurpose
Python>= 3.10Runtime for the tool
Go>= 1.16Required for go build commands
uvLatestPackage manager and test runner
GitAny recentVersion control

Sources: spec.md:95-100

Repository Structure

go-to-wheel/
├── go_to_wheel/
│   └── __init__.py      # Main module with all logic
├── tests/
│   └── test_*.py        # Test files
├── pyproject.toml       # Project configuration
├── README.md            # User documentation
├── spec.md              # Technical specification
└── LICENSE              # Apache 2.0 license

The main implementation resides in go_to_wheel/__init__.py, which contains:

  • Platform mapping definitions
  • CLI argument parsing
  • Go cross-compilation logic
  • Wheel building functions
  • Metadata generation

Sources: go_to_wheel/__init__.py:1-50

Setting Up the Development Environment

1. Clone the Repository

git clone https://github.com/simonw/go-to-wheel
cd go-to-wheel

Sources: README.md:55-58

2. Install Dependencies

The project uses uv for dependency management. Install all development dependencies:

uv sync

This automatically installs the project and its test dependencies based on pyproject.toml.

3. Verify Installation

Confirm the tool is accessible:

uv run go-to-wheel --version

Running Tests

The project uses pytest for testing. Execute the full test suite with:

uv run pytest

Sources: README.md:59-60

Test Structure

Tests are located in the tests/ directory and follow the naming pattern test_*.py. Each test file typically corresponds to a major component:

Test FileCoverage Area
test_platforms.pyPlatform mapping and validation
test_build.pyWheel building logic
test_cli.pyCommand-line argument parsing

Running Specific Tests

Run tests matching a pattern:

uv run pytest -k "test_name_pattern"

Run with verbose output:

uv run pytest -v

Code Architecture

Core Components

graph TD
    A[CLI Entry Point] --> B[Argument Parser]
    B --> C[build_wheels Function]
    C --> D[Platform Validation]
    C --> E[Go Cross-Compilation]
    E --> F[Wheel Assembly]
    F --> G[Metadata Generation]
    F --> H[ZIP Creation]
    G --> H
    H --> I[Output Wheels]

Platform Mapping System

The tool defines platform mappings that translate human-readable platform names to Go environment variables and wheel tags:

PLATFORM_MAPPINGS: dict[str, tuple[str, str, str]] = {
    "linux-amd64": ("linux", "amd64", "manylinux_2_17_x86_64"),
    "linux-arm64": ("linux", "arm64", "manylinux_2_17_aarch64"),
    "linux-amd64-musl": ("linux", "amd64", "musllinux_1_2_x86_64"),
    "linux-arm64-musl": ("linux", "arm64", "musllinux_1_2_aarch64"),
    "darwin-amd64": ("darwin", "amd64", "macosx_10_9_x86_64"),
    "darwin-arm64": ("darwin", "arm64", "macosx_11_0_arm64"),
    "windows-amd64": ("windows", "amd64", "win_amd64"),
    "windows-arm64": ("windows", "arm64", "win_arm64"),
}

Sources: go_to_wheel/__init__.py:19-27

Default Platforms

graph LR
    A[Default Platforms] --> B[Linux amd64]
    A --> C[Linux arm64]
    A --> D[Linux amd64-musl]
    A --> E[Linux arm64-musl]
    A --> F[macOS amd64]
    A --> G[macOS arm64]
    A --> H[Windows amd64]
    A --> I[Windows arm64]

The DEFAULT_PLATFORMS list defines which platforms are built when none are specified:

DEFAULT_PLATFORMS = [
    "linux-amd64",
    "linux-arm64",
    "linux-amd64-musl",
    "linux-arm64-musl",
    "darwin-amd64",
    "darwin-arm64",
    "windows-amd64",
    "windows-arm64",
]

Sources: go_to_wheel/__init__.py:29-37

Build Workflow

sequenceDiagram
    participant User
    participant CLI
    participant Builder
    participant Go
    participant WheelBuilder
    
    User->>CLI: go-to-wheel ./myapp
    CLI->>Builder: build_wheels(go_dir)
    Builder->>Go: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build
    Go-->>Builder: Binary for linux/amd64
    Builder->>Go: (repeat for each platform)
    Go-->>Builder: Binaries for all platforms
    Builder->>WheelBuilder: Create wheel structure
    WheelBuilder->>WheelBuilder: Generate __init__.py
    WheelBuilder->>WheelBuilder: Generate METADATA
    WheelBuilder->>WheelBuilder: Create RECORD with hashes
    WheelBuilder->>Builder: Wheel file
    Builder->>User: List of wheel paths

Building Wheels Locally

Local Installation

Install the package in development mode:

uv pip install -e .

Building Wheels for Testing

Create test wheels for a sample Go module:

go-to-wheel ./path/to/go-module --name my-test-package

This creates wheels in ./dist for all default platforms. Sources: README.md:31-37

Custom Platform Selection

Build only specific platforms to speed up iteration:

go-to-wheel ./myapp --platforms linux-amd64,darwin-arm64

CLI Argument Reference

The tool accepts the following arguments for development and testing:

ArgumentTypeDefaultPurpose
--namestringDirectory namePython package name
--versionstring0.1.0Package version
--output-dirstring./distOutput directory
--platformsstringAll platformsTarget platforms
--go-binarystringgoGo binary path
--ldflagsstringNoneAdditional linker flags

Sources: go_to_wheel/__init__.py:75-100

Cross-Compilation Process

The build process uses Go's cross-compilation with specific environment settings:

def compile_go_binary(
    go_dir: str,
    goos: str,
    goarch: str,
    output_path: str,
    go_binary: str,
    ldflags: str | None = None,
) -> None:
    env = os.environ.copy()
    env["GOOS"] = goos
    env["GOARCH"] = goarch
    env["CGO_ENABLED"] = "0"  # Static binaries
    
    ldflags_value = "-s -w"  # Strip debug info
    if ldflags:
        ldflags_value += " " + ldflags
    
    cmd = [go_binary, "build", f"-ldflags={ldflags_value}", "-o", output_path, "."]

Key points:

  • CGO_ENABLED=0 ensures static binaries with no libc dependency
  • -ldflags="-s -w" strips debug information to reduce binary size
  • Windows builds automatically receive .exe extension

Sources: go_to_wheel/__init__.py:120-140

Wheel Structure Generation

Each wheel contains a Python wrapper that executes the bundled binary:

graph TD
    subgraph Wheel Contents
        A[Package Directory] --> B[__init__.py]
        A --> C[__main__.py]
        A --> D[bin/]
        D --> E[Go Binary]
        A --> F[.dist-info/]
        F --> G[METADATA]
        F --> H[WHEEL]
        F --> I[RECORD]
        F --> J[entry_points.txt]
    end

The __init__.py includes:

def get_binary_path():
    """Return the path to the bundled binary."""
    return os.path.join(os.path.dirname(__file__), "bin", "{binary_name}")

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

Sources: spec.md:58-75

Version Information

The project version is defined in the main module:

__version__ = "0.1.0"

Sources: go_to_wheel/__init__.py:12

To check the installed version during development:

uv run python -c "import go_to_wheel; print(go_to_wheel.__version__)"

Debugging

Enable Verbose Output

The CLI outputs progress during builds:

go-to-wheel v0.1.0
Building from ./myapp

linux-amd64... done
darwin-arm64... done

Common Build Failures

IssueCauseSolution
go.mod not foundDirectory is not a Go moduleRun go mod init first
Go not foundGo not in PATHInstall Go or use --go-binary
Build timeoutComplex Go projectIncrease timeout or skip platforms

Inspecting Generated Wheels

List contents of a wheel:

unzip -l myapp-1.0.0-py3-none-manylinux_2_17_x86_64.whl

Contributing

Development Workflow

  1. Fork the repository on GitHub
  2. Clone your fork locally
  3. Create a feature branch: git checkout -b feature/my-feature
  4. Make changes and add tests
  5. Run the test suite: uv run pytest
  6. Commit your changes with clear messages
  7. Push to your fork and submit a pull request

Code Style

  • Follow PEP 8 for Python code
  • Use type hints for function parameters and return values
  • Add docstrings to public functions
  • Keep functions focused and small

Testing Guidelines

  • All new features should include tests
  • Ensure existing tests pass before submitting
  • Use descriptive test names: test_platform_mapping_linux_amd64

Future Development Considerations

The specification outlines potential future features not yet implemented:

FeatureStatusDescription
Configuration fileNot startedSupport pyproject.toml or go-to-wheel.toml
Auto version detectionNot startedParse from VERSION file or git tags
Multiple entry pointsNot startedSupport Go modules with multiple binaries
Universal2 macOSNot startedCombine arm64 and x86_64 with lipo
Build cachingNot startedSkip unchanged platforms
PyPI publishingNot startedAdd go-to-wheel publish command

Sources: spec.md:80-93

Sources: spec.md:95-100

Configuration and Metadata

Related topics: CLI Options Reference, Development Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Supported Platforms

Continue reading this section for the full explanation and source context.

Section Platform Selection

Continue reading this section for the full explanation and source context.

Section Build Workflow

Continue reading this section for the full explanation and source context.

Related topics: CLI Options Reference, Development Guide

Configuration and Metadata

This page documents the configuration system and metadata handling in go-to-wheel, a tool that compiles Go CLI programs into Python wheels.

Overview

go-to-wheel provides extensive configuration options for customizing wheel builds. Configuration is passed through command-line arguments and is used to generate proper Python wheel metadata according to PEP 427 and PEP 566 standards.

The tool supports two layers of configuration:

  1. Build Configuration - Controls how the Go binary is compiled (platforms, Go binary path, ldflags)
  2. Package Metadata - Defines Python package metadata (name, version, author, license, etc.)

Sources: go_to_wheel/__init__.py:1-50

Platform Configuration

Supported Platforms

go-to-wheel supports cross-compilation for 8 different platform configurations:

Platform IdentifierGOOSGOARCHWheel Tag
linux-amd64linuxamd64manylinux_2_17_x86_64
linux-arm64linuxarm64manylinux_2_17_aarch64
linux-amd64-musllinuxamd64musllinux_1_2_x86_64
linux-arm64-musllinuxarm64musllinux_1_2_aarch64
darwin-amd64darwinamd64macosx_10_9_x86_64
darwin-arm64darwinarm64macosx_11_0_arm64
windows-amd64windowsamd64win_amd64
windows-arm64windowsarm64win_arm64

Sources: go_to_wheel/__init__.py:22-30

Platform Selection

By default, all 8 platforms are built. Users can specify a subset using the --platforms option:

go-to-wheel ./mytool --platforms linux-amd64,darwin-arm64

Platform identifiers are defined in the PLATFORM_MAPPINGS dictionary and can be parsed from comma-separated input.

Sources: go_to_wheel/__init__.py:86-90

Build Workflow

graph TD
    A[Parse --platforms argument] --> B{Platforms specified?}
    B -->|No| C[Use DEFAULT_PLATFORMS]
    B -->|Yes| D[Split by comma]
    D --> E[Validate each platform]
    E --> F[For each platform]
    F --> G[Set GOOS/GOARCH env]
    G --> H[Run go build with CGO_ENABLED=0]
    H --> I[Create wheel structure]
    I --> J[Generate METADATA, WHEEL, RECORD]
    J --> K[Zip into .whl file]
    K --> F

Command-Line Options

Full Option Reference

OptionTypeDefaultDescription
--namestringDirectory basenamePython package name
--versionstring0.1.0Package version
--output-dirstring./distDirectory for built wheels
--entry-pointstringSame as --nameCLI command name
--platformsstringAll 8 platformsComma-separated platform list
--go-binarystringgoPath to Go binary
--descriptionstring"Go binary packaged as Python wheel"Package description
--licensestringNoneSPDX license identifier
--authorstringNoneAuthor name
--author-emailstringNoneAuthor email
--urlstringNoneProject URL
--requires-pythonstring>=3.10Python version requirement
--readmestringNonePath to README markdown file
--ldflagsstringNoneAdditional Go linker flags
--set-version-varstringNoneGo variable for version embedding

Sources: go_to_wheel/__init__.py:52-115

Build Configuration Options

These options control the Go compilation process:

def build_wheels(
    go_dir: str,
    *,
    name: str | None = None,
    version: str = "0.1.0",
    output_dir: str = "./dist",
    entry_point: str | None = None,
    platforms: list[str] | None = None,
    go_binary: str = "go",
    description: str = "Go binary packaged as Python wheel",
    # ... metadata options ...
    ldflags: str | None = None,
    set_version_var: str | None = None,
) -> list[str]:

Sources: go_to_wheel/__init__.py:178-220

Linker Flags Configuration

The --ldflags and --set-version-var options control Go linker flags:

Default flags: -s -w (strips debug info and symbol tables)

Additional flags can be appended using --ldflags:

go-to-wheel ./mytool --ldflags "-X main.commit=abc123"

Version embedding via --set-version-var:

go-to-wheel ./mytool --version 2.0.0 --set-version-var main.version

This passes -X main.version=2.0.0 to the Go linker.

Sources: README.md:30-50

The linker flags are combined in this order:

  1. -s -w (default strip flags)
  2. -X {set_version_var}={version} (if --set-version-var is set)
  3. User-provided --ldflags (appended last)

Sources: go_to_wheel/__init__.py:255-265

Metadata Generation

METADATA File (PEP 566)

Generated by generate_metadata():

def generate_metadata(
    name: str,
    version: str,
    description: str,
    *,
    author: str | None = None,
    author_email: str | None = None,
    license_: str | None = None,
    url: str | None = None,
    requires_python: str = ">=3.10",
    readme_content: str | None = None,
) -> str:
    """Generate METADATA file content."""
    lines = [
        "Metadata-Version: 2.1",
        f"Name: {name}",
        f"Version: {version}",
        f"Summary: {description}",
    ]

    if author:
        lines.append(f"Author: {author}")
    if author_email:
        lines.append(f"Author-email: {author_email}")
    if license_:
        lines.append(f"License: {license_}")
    if url:
        lines.append(f"Home-page: {url}")

    lines.append(f"Requires-Python: {requires_python}")

    if readme_content:
        lines.append("Description-Content-Type: text/markdown")
        lines.append("")
        lines.append(readme_content)

    return "\n".join(lines) + "\n"

Sources: go_to_wheel/__init__.py:280-315

WHEEL File (PEP 427)

Generated by generate_wheel_metadata():

Wheel-Version: 1.0
Generator: go-to-wheel {version}
Root-Is-Purelib: false
Tag: py3-none-{platform_tag}

Key points:

  • Root-Is-Purelib: false indicates platform-specific binary
  • py3-none indicates Python 3+ requirement
  • {platform_tag} varies by target (e.g., manylinux_2_17_x86_64)

Sources: go_to_wheel/__init__.py:317-325

entry_points.txt

Generated for console script entry points:

[console_scripts]
{entry_point} = {import_name}:main

The import_name is derived from the package name by replacing hyphens with underscores.

Sources: go_to_wheel/__init__.py:327-332

RECORD File

Generated by generate_record() using SHA256 hashes:

def generate_record(files: dict[str, bytes]) -> str:
    """Generate RECORD file content."""
    output = io.StringIO()
    writer = csv.writer(output)

    for path, content in files.items():
        if path.endswith("RECORD"):
            writer.writerow([path, "", ""])
        else:
            hash_value = base64.urlsafe_b64encode(
                hashlib.sha256(content).digest()
            ).rstrip(b"=").decode("ascii")
            writer.writerow([path, f"sha256={hash_value}", len(content)])

    return output.getvalue()

Sources: go_to_wheel/__init__.py:334-348

Package Structure

The generated wheel follows this structure:

{package_name}-{version}-py3-none-{platform_tag}.whl
├── {import_name}/
│   ├── __init__.py
│   ├── __main__.py
│   └── bin/
│       └── {binary_name}[.exe]
├── {import_name}-{version}.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── entry_points.txt

Sources: spec.md:60-80

__init__.py Template

"""Go binary packaged as Python wheel."""

import os
import sys
import subprocess

__version__ = "{version}"

def get_binary_path():
    """Return the path to the bundled binary."""
    return os.path.join(os.path.dirname(__file__), "bin", "{binary_name}")

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

Sources: spec.md:82-100

Metadata Flow

graph LR
    A[CLI Arguments] --> B[ArgumentParser]
    B --> C[build_wheels function]
    C --> D[Input Validation]
    D --> E[README Processing]
    E --> F[Platform Loop]
    F --> G[Go Compilation]
    G --> H[Wheel Assembly]
    H --> I[Metadata Generation]
    I --> J[ZIP Creation]
    J --> K[Output Directory]
    
    C -->|name, version| L[METADATA]
    C -->|platform_tag| M[WHEEL]
    C -->|entry_point| N[entry_points.txt]
    H --> O[RECORD with hashes]

Input Validation

The build_wheels() function validates inputs:

CheckBehavior on Failure
Go directory existsRaises FileNotFoundError
go.mod file presentRaises ValueError
README file exists (if provided)Raises FileNotFoundError
Package name valid (PEP 503)Raises ValueError

Sources: go_to_wheel/__init__.py:225-235

Configuration Precedence

When multiple configuration sources conflict, the following precedence applies:

  1. Command-line arguments (highest priority)
  2. Default values (lowest priority)

For --ldflags, user-provided flags are appended after default flags, allowing overrides.

Environment Variables Used

During build, these environment variables are set:

VariableValuePurpose
GOOSPlatform GOOSCross-compilation target
GOARCHPlatform GOARCHCross-compilation target
CGO_ENABLED0Static binary compilation

Sources: go_to_wheel/__init__.py:270-280

Example: Full Metadata Configuration

go-to-wheel ./mytool \
  --name mytool-bin \
  --version 2.0.0 \
  --description "My awesome tool" \
  --license MIT \
  --author "Jane Doe" \
  --author-email "[email protected]" \
  --url "https://github.com/jane/mytool" \
  --readme README.md

This produces a wheel with complete PyPI-compatible metadata.

Sources: README.md:55-65

Sources: go_to_wheel/__init__.py:1-50

Doramagic Pitfall Log

Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.

medium README/documentation is current enough for a first validation pass.

The project should not be treated as fully validated until this signal is reviewed.

medium Maintainer activity is unknown

Users cannot judge support quality until recent activity, releases, and issue response are checked.

medium no_demo

The project may affect permissions, credentials, data exposure, or host boundaries.

medium no_demo

The project may affect permissions, credentials, data exposure, or host boundaries.

Doramagic Pitfall Log

Doramagic extracted 6 source-linked risk signals. Review them before installing or handing real data to the project.

1. Capability assumption: README/documentation is current enough for a first validation pass.

  • Severity: medium
  • Finding: README/documentation is current enough for a first validation pass.
  • User impact: The project should not be treated as fully validated until this signal is reviewed.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: capability.assumptions | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | README/documentation is current enough for a first validation pass.

2. Maintenance risk: Maintainer activity is unknown

  • Severity: medium
  • Finding: Maintenance risk is backed by a source signal: Maintainer activity is unknown. Treat it as a review item until the current version is checked.
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | last_activity_observed missing

3. Security or permission risk: no_demo

  • Severity: medium
  • Finding: no_demo
  • User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: downstream_validation.risk_items | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | no_demo; severity=medium

4. Security or permission risk: no_demo

  • Severity: medium
  • Finding: no_demo
  • User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: risks.scoring_risks | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | no_demo; severity=medium

5. Maintenance risk: issue_or_pr_quality=unknown

  • Severity: low
  • Finding: issue_or_pr_quality=unknown。
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | issue_or_pr_quality=unknown

6. Maintenance risk: release_recency=unknown

  • Severity: low
  • Finding: release_recency=unknown。
  • User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
  • Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
  • Evidence: evidence.maintainer_signals | hn_item:48109677 | https://news.ycombinator.com/item?id=48109677 | release_recency=unknown

Source: Doramagic discovery, validation, and Project Pack records

Community Discussion Evidence

These external discussion links are review inputs, not standalone proof that the project is production-ready.

Sources 1

Count of project-level external discussion links exposed on this manual page.

Use Review before install

Open the linked issues or discussions before treating the pack as ready for your environment.

Community Discussion Evidence

Doramagic exposes project-level community discussion separately from official documentation. Review these links before using go-to-wheel with real data or production workflows.

Source: Project Pack community evidence and pitfall evidence