Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,30 @@ When reporting issues, please include:
If you're adding a new language plugin:

1. Create a new package under `plugins/<language>/`
2. Implement the `core.Plugin` interface
2. Implement the `core.Plugin` interface:
```go
type Plugin interface {
// Name returns the plugin name (e.g., "cgo", "python")
Name() string

// Generate produces binding code for the given parsed package
Generate(pkg *ParsedPackage) ([]byte, error)

// Build generates code and compiles/packages it for distribution
Build(pkg *ParsedPackage, inputPath string, opts *BuildOptions) error
}
```
3. Register the plugin in `init()` using `factory.Register()`
4. Add comprehensive tests
4. Add comprehensive tests for both `Generate` and `Build` methods
5. Update documentation in `docs/`

### Plugin Implementation Tips

- The `Generate` method should return the generated source code as bytes
- The `Build` method should handle the full build pipeline (generate code, compile, package)
- Use `core.BuildOptions` to access output directory, library name, build system, and verbose flag
- For languages that need a shared library (like Python), call the CGO plugin's `Build` method first

## Questions?

Feel free to open an issue for questions or discussions about contributing.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# GoAnywhere

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/riceriley59/goanywhere/actions/workflows/ci.yaml/badge.svg)](https://github.com/riceriley59/goanywhere/actions/workflows/ci.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/riceriley59/goanywhere)](https://goreportcard.com/report/github.com/riceriley59/goanywhere)
[![Go Version](https://img.shields.io/badge/go-1.23-blue.svg)](https://golang.org/)
[![Coverage Status](https://coveralls.io/repos/github/riceriley59/goanywhere/badge.svg?branch=main)](https://coveralls.io/github/riceriley59/goanywhere?branch=main)
[![Go Version](https://img.shields.io/badge/go-1.25.6-blue.svg)](https://golang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Generate language bindings for your Go libraries to use them from Python, C, and other languages.

Expand Down Expand Up @@ -40,8 +41,11 @@ goanywhere generate ./mypackage
# Generate Python bindings
goanywhere generate ./mypackage --plugin python

# Specify output file
goanywhere generate ./mypackage -o ./bindings/main.go
# Build CGO shared library directly
goanywhere build ./mypackage --plugin cgo

# Build Python package with shared library
goanywhere build ./mypackage --plugin python
```

## Documentation
Expand Down
97 changes: 96 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Usage Guide

## Command Overview
## Commands

GoAnywhere provides two main commands:

- `generate` - Generate language binding source code
- `build` - Generate and compile bindings into distributable packages

## Generate Command

```bash
goanywhere generate <input-directory> [flags]
Expand All @@ -15,6 +22,83 @@ goanywhere generate <input-directory> [flags]
| `--plugin` | `-p` | Plugin type (`cgo`, `python`) | `cgo` |
| `--verbose` | `-v` | Show parsed constructs and skipped items | `false` |

## Build Command

The `build` command generates binding code and compiles it into ready-to-use packages.

```bash
goanywhere build <input-directory> [flags]
```

### Flags

| Flag | Short | Description | Default |
|------|-------|-------------|---------|
| `--output` | `-o` | Output directory for built artifacts | `<input>/<plugin>_build` |
| `--import-path` | `-i` | Import path for the target package | Auto-detected from go.mod |
| `--plugin` | `-p` | Plugin type (`cgo`, `python`) | `cgo` |
| `--build-system` | | Python build system (`setuptools`, `hatch`, `poetry`, `uv`) | `setuptools` |
| `--lib-name` | | Override the default library name | `lib<package>` |
| `--verbose` | `-v` | Show build progress and details | `false` |

### CGO Build

Build a shared library from your Go package:

```bash
goanywhere build ./mypackage --plugin cgo
```

This generates CGO bindings and compiles them into a shared library (`.so` on Linux, `.dylib` on macOS, `.dll` on Windows).

### Python Build

Build a complete Python package with shared library:

```bash
goanywhere build ./mypackage --plugin python
```

This creates a distributable Python package structure:

```
mypackage/python_build/
├── mypackage/
│ ├── __init__.py
│ ├── bindings.py
│ └── lib/
│ └── libmypackage.so
├── cgo_plugin/
│ └── main.go
├── libmypackage.so
└── pyproject.toml
```

### Python Build Systems

Choose your preferred Python build system:

```bash
# setuptools (default)
goanywhere build ./mypackage --plugin python --build-system setuptools

# hatch
goanywhere build ./mypackage --plugin python --build-system hatch

# poetry
goanywhere build ./mypackage --plugin python --build-system poetry

# uv
goanywhere build ./mypackage --plugin python --build-system uv
```

After building, install the package:

```bash
cd mypackage/python_build
pip install -e .
```

## Examples

### Basic Usage
Expand Down Expand Up @@ -61,6 +145,8 @@ goanywhere generate ./mypackage -v

## Building the Generated Code

> **Tip:** Use `goanywhere build` to automate these steps. See [Build Command](#build-command).

### CGO Shared Library

After generating CGO bindings, build a shared library:
Expand Down Expand Up @@ -131,7 +217,16 @@ mypackage/

## Workflow

### Using Generate (Manual Build)

1. Write your Go library with exported functions and structs
2. Run `goanywhere generate ./mypackage`
3. Build the shared library with `go build -buildmode=c-shared`
4. Use the library from C, Python, or other languages

### Using Build (Automated)

1. Write your Go library with exported functions and structs
2. Run `goanywhere build ./mypackage --plugin python`
3. Install the package with `pip install -e ./mypackage/python_build`
4. Import and use in Python
141 changes: 141 additions & 0 deletions internal/cli/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package cli

import (
"fmt"
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/riceriley59/goanywhere/internal/core"
"github.com/riceriley59/goanywhere/internal/core/factory"
)

type buildOptions struct {
OutputDir string
ImportPath string
Plugin string
BuildSystem string
LibraryName string
Verbose bool
}

// NewBuildCmd creates the build subcommand
func NewBuildCmd() *cobra.Command {
opts := &buildOptions{}

cmd := &cobra.Command{
Use: "build <input-directory>",
Short: "Generate and build plugin code for a Go package",
Long: `Generate plugin code and build it as a shared library or package.

For CGO plugin:
Generates the CGO wrapper code and compiles it to a shared library (.so/.dylib/.dll)

For Python plugin:
Generates CGO shared library, Python bindings, and creates a Python package
with the specified build system configuration.

Examples:
goanywhere build ./mypackage --plugin cgo
goanywhere build ./mypackage --plugin cgo -o ./dist
goanywhere build ./mypackage --plugin python --build-system setuptools`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runBuild(args[0], opts)
},
}

cmd.Flags().StringVarP(&opts.OutputDir, "output", "o", "",
"Output directory (default: <input>/<plugin>_build)")
cmd.Flags().StringVarP(&opts.ImportPath, "import-path", "i", "",
"Import path for the target package (required for proper imports)")
cmd.Flags().StringVarP(&opts.Plugin, "plugin", "p", "cgo",
"Plugin type to build (cgo, python)")
cmd.Flags().StringVar(&opts.BuildSystem, "build-system", "setuptools",
"Python build system (setuptools, hatch, poetry, uv)")
cmd.Flags().StringVar(&opts.LibraryName, "lib-name", "",
"Override the shared library name (default: lib<package>)")
cmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false,
"Verbose output")

return cmd
}

func runBuild(inputDir string, opts *buildOptions) error {
// Resolve input path
inputPath, err := filepath.Abs(inputDir)
if err != nil {
return fmt.Errorf("invalid input path: %w", err)
}

// Check if input directory exists
info, err := os.Stat(inputPath)
if err != nil {
return fmt.Errorf("cannot access input directory: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("input path is not a directory: %s", inputPath)
}

// Parse the Go package
parser := core.NewParser(opts.Verbose)
if opts.Verbose {
fmt.Printf("Parsing package at: %s\n", inputPath)
}

pkg, err := parser.ParsePackage(inputPath)
if err != nil {
return fmt.Errorf("parse error: %w", err)
}

// Set import path
if opts.ImportPath != "" {
pkg.ImportPath = opts.ImportPath
} else {
importPath, err := inferImportPath(inputPath)
if err == nil {
pkg.ImportPath = importPath
} else {
return fmt.Errorf("could not determine import path: use --import-path flag")
}
}

if opts.Verbose {
fmt.Printf("Package: %s\n", pkg.Name)
fmt.Printf("Import path: %s\n", pkg.ImportPath)
fmt.Printf("Functions: %d\n", len(pkg.Functions))
fmt.Printf("Structs: %d\n", len(pkg.Structs))
}

// Determine output directory
outputDir := opts.OutputDir
if outputDir == "" {
outputDir = filepath.Join(inputPath, opts.Plugin+"_build")
}
outputDir, err = filepath.Abs(outputDir)
if err != nil {
return fmt.Errorf("invalid output path: %w", err)
}

// Create output directory
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("cannot create output directory: %w", err)
}

// Get the plugin
plugin, err := factory.Get(opts.Plugin, opts.Verbose)
if err != nil {
return fmt.Errorf("unsupported plugin for build: %s (supported: cgo, python)", opts.Plugin)
}

// Build using the plugin
buildOpts := &core.BuildOptions{
OutputDir: outputDir,
LibraryName: opts.LibraryName,
BuildSystem: opts.BuildSystem,
Verbose: opts.Verbose,
}

return plugin.Build(pkg, inputPath, buildOpts)
}
1 change: 1 addition & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func NewGoAnywhereCmd() *cobra.Command {

// Add subcommands
goAnywhereCmd.AddCommand(NewGenerateCmd())
goAnywhereCmd.AddCommand(NewBuildCmd())

return goAnywhereCmd
}
Expand Down
Loading
Loading