feat: scaffold purego-pipewire generator repo
- commit
- c274f181da87d1239b326d991a62c31a6a093898
- author
- brice · · 2026-04-09 05:08 UTC
- parent
- none (root commit)
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
15 files · +187 -0
A .mockery.yml +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | with-expecter: true | |
| 2 | + | dir: "{{.InterfaceDir}}/mocks" | |
| 3 | + | outpkg: mocks | |
| 4 | + | packages: | |
| 5 | + | github.com/bnema/purego-pipewire/internal/ports/out: | |
| 6 | + | interfaces: | |
| 7 | + | CAPI: | |
| 8 | + | InitAPI: | |
| 9 | + | LoopAPI: | |
| 10 | + | ContextAPI: | |
| 11 | + | CoreAPI: | |
| 12 | + | RegistryAPI: | |
| 13 | + | StreamAPI: | |
| 14 | + | github.com/bnema/purego-pipewire/pipewire: | |
| 15 | + | interfaces: | |
| 16 | + | Runtime: | |
| 17 | + | MainLoop: | |
| 18 | + | Context: | |
| 19 | + | Core: | |
| 20 | + | Registry: | |
| 21 | + | Stream: | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | with-expecter: true | |||
| 2 | + | dir: "{{.InterfaceDir}}/mocks" | |||
| 3 | + | outpkg: mocks | |||
| 4 | + | packages: | |||
| 5 | + | github.com/bnema/purego-pipewire/internal/ports/out: | |||
| 6 | + | interfaces: | |||
| 7 | + | CAPI: | |||
| 8 | + | InitAPI: | |||
| 9 | + | LoopAPI: | |||
| 10 | + | ContextAPI: | |||
| 11 | + | CoreAPI: | |||
| 12 | + | RegistryAPI: | |||
| 13 | + | StreamAPI: | |||
| 14 | + | github.com/bnema/purego-pipewire/pipewire: | |||
| 15 | + | interfaces: | |||
| 16 | + | Runtime: | |||
| 17 | + | MainLoop: | |||
| 18 | + | Context: | |||
| 19 | + | Core: | |||
| 20 | + | Registry: | |||
| 21 | + | Stream: | |||
A README.md +31 -0
| @@ -0,0 +1,31 @@ | |||
| 1 | + | # purego-pipewire | |
| 2 | + | ||
| 3 | + | Pure Go bindings for PipeWire using [purego](https://github.com/ebitengine/purego). | |
| 4 | + | ||
| 5 | + | ## Architecture | |
| 6 | + | ||
| 7 | + | This package follows the same layer split as other purego-* projects: | |
| 8 | + | ||
| 9 | + | - `internal/capi` — generated raw function types and registration helpers | |
| 10 | + | - `internal/ports/{in,out}` — generated port interfaces | |
| 11 | + | - `internal/core` — handwritten lifecycle logic | |
| 12 | + | - `internal/loader` — `dlopen` for PipeWire and SPA libs | |
| 13 | + | - `pipewire/` — public Go-facing wrappers | |
| 14 | + | - `spa/` — public constants and helpers | |
| 15 | + | ||
| 16 | + | ## Regenerating bindings | |
| 17 | + | ||
| 18 | + | Normal contributors should run: | |
| 19 | + | ||
| 20 | + | go generate ./... | |
| 21 | + | ||
| 22 | + | This uses checked-in model files under `gen/` (e.g. `gen/pipewire.json`). | |
| 23 | + | ||
| 24 | + | Mocks are generated by [mockery](https://github.com/vektra/mockery): | |
| 25 | + | ||
| 26 | + | mockery | |
| 27 | + | ||
| 28 | + | ## Requirements | |
| 29 | + | ||
| 30 | + | - Go 1.24+ | |
| 31 | + | - Linux with PipeWire 0.3 runtime libraries | |
| @@ -0,0 +1,31 @@ | |||||
| 1 | + | # purego-pipewire | |||
| 2 | + | ||||
| 3 | + | Pure Go bindings for PipeWire using [purego](https://github.com/ebitengine/purego). | |||
| 4 | + | ||||
| 5 | + | ## Architecture | |||
| 6 | + | ||||
| 7 | + | This package follows the same layer split as other purego-* projects: | |||
| 8 | + | ||||
| 9 | + | - `internal/capi` — generated raw function types and registration helpers | |||
| 10 | + | - `internal/ports/{in,out}` — generated port interfaces | |||
| 11 | + | - `internal/core` — handwritten lifecycle logic | |||
| 12 | + | - `internal/loader` — `dlopen` for PipeWire and SPA libs | |||
| 13 | + | - `pipewire/` — public Go-facing wrappers | |||
| 14 | + | - `spa/` — public constants and helpers | |||
| 15 | + | ||||
| 16 | + | ## Regenerating bindings | |||
| 17 | + | ||||
| 18 | + | Normal contributors should run: | |||
| 19 | + | ||||
| 20 | + | go generate ./... | |||
| 21 | + | ||||
| 22 | + | This uses checked-in model files under `gen/` (e.g. `gen/pipewire.json`). | |||
| 23 | + | ||||
| 24 | + | Mocks are generated by [mockery](https://github.com/vektra/mockery): | |||
| 25 | + | ||||
| 26 | + | mockery | |||
| 27 | + | ||||
| 28 | + | ## Requirements | |||
| 29 | + | ||||
| 30 | + | - Go 1.24+ | |||
| 31 | + | - Linux with PipeWire 0.3 runtime libraries | |||
A cmd/pipewiregen/internal/emitter/emitter.go +2 -0
| @@ -0,0 +1,2 @@ | |||
| 1 | + | // Package emitter renders generated Go source files from the binding model. | |
| 2 | + | package emitter | |
| @@ -0,0 +1,2 @@ | |||||
| 1 | + | // Package emitter renders generated Go source files from the binding model. | |||
| 2 | + | package emitter | |||
A cmd/pipewiregen/internal/emitter/emitter_test.go +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | package emitter | |
| @@ -0,0 +1,1 @@ | |||||
| 1 | + | package emitter | |||
A cmd/pipewiregen/internal/emitter/templates/model_summary.tmpl +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | {{- /* model_summary.tmpl: debugging template for model inspection */ -}} | |
| 2 | + | Libraries: {{ len .Libraries }} | |
| 3 | + | Groups: {{ len .Groups }} | |
| 4 | + | Symbols: {{ len .Symbols }} | |
| @@ -0,0 +1,4 @@ | |||||
| 1 | + | {{- /* model_summary.tmpl: debugging template for model inspection */ -}} | |||
| 2 | + | Libraries: {{ len .Libraries }} | |||
| 3 | + | Groups: {{ len .Groups }} | |||
| 4 | + | Symbols: {{ len .Symbols }} | |||
A cmd/pipewiregen/internal/model/model.go +27 -0
| @@ -0,0 +1,27 @@ | |||
| 1 | + | package model | |
| 2 | + | ||
| 3 | + | // Model represents the checked-in binding model for PipeWire/SPA code generation. | |
| 4 | + | type Model struct { | |
| 5 | + | Libraries []Library `json:"libraries"` | |
| 6 | + | Groups []Group `json:"groups"` | |
| 7 | + | Symbols []Symbol `json:"symbols"` | |
| 8 | + | } | |
| 9 | + | ||
| 10 | + | // Library represents a shared library to load at runtime. | |
| 11 | + | type Library struct { | |
| 12 | + | Name string `json:"name"` | |
| 13 | + | SOName string `json:"soname"` | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | // Group represents a logical grouping of symbols. | |
| 17 | + | type Group struct { | |
| 18 | + | Name string `json:"name"` | |
| 19 | + | Symbols []string `json:"symbols"` | |
| 20 | + | } | |
| 21 | + | ||
| 22 | + | // Symbol represents a single C function to bind. | |
| 23 | + | type Symbol struct { | |
| 24 | + | Name string `json:"name"` | |
| 25 | + | Library string `json:"library"` | |
| 26 | + | Signature string `json:"signature"` | |
| 27 | + | } | |
| @@ -0,0 +1,27 @@ | |||||
| 1 | + | package model | |||
| 2 | + | ||||
| 3 | + | // Model represents the checked-in binding model for PipeWire/SPA code generation. | |||
| 4 | + | type Model struct { | |||
| 5 | + | Libraries []Library `json:"libraries"` | |||
| 6 | + | Groups []Group `json:"groups"` | |||
| 7 | + | Symbols []Symbol `json:"symbols"` | |||
| 8 | + | } | |||
| 9 | + | ||||
| 10 | + | // Library represents a shared library to load at runtime. | |||
| 11 | + | type Library struct { | |||
| 12 | + | Name string `json:"name"` | |||
| 13 | + | SOName string `json:"soname"` | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | // Group represents a logical grouping of symbols. | |||
| 17 | + | type Group struct { | |||
| 18 | + | Name string `json:"name"` | |||
| 19 | + | Symbols []string `json:"symbols"` | |||
| 20 | + | } | |||
| 21 | + | ||||
| 22 | + | // Symbol represents a single C function to bind. | |||
| 23 | + | type Symbol struct { | |||
| 24 | + | Name string `json:"name"` | |||
| 25 | + | Library string `json:"library"` | |||
| 26 | + | Signature string `json:"signature"` | |||
| 27 | + | } | |||
A cmd/pipewiregen/internal/parser/parser.go +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | package parser | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/json" | |
| 5 | + | "os" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // Load reads and parses a binding model JSON file. | |
| 11 | + | func Load(path string) (*model.Model, error) { | |
| 12 | + | b, err := os.ReadFile(path) | |
| 13 | + | if err != nil { | |
| 14 | + | return nil, err | |
| 15 | + | } | |
| 16 | + | var m model.Model | |
| 17 | + | if err := json.Unmarshal(b, &m); err != nil { | |
| 18 | + | return nil, err | |
| 19 | + | } | |
| 20 | + | return &m, nil | |
| 21 | + | } | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | package parser | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/json" | |||
| 5 | + | "os" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // Load reads and parses a binding model JSON file. | |||
| 11 | + | func Load(path string) (*model.Model, error) { | |||
| 12 | + | b, err := os.ReadFile(path) | |||
| 13 | + | if err != nil { | |||
| 14 | + | return nil, err | |||
| 15 | + | } | |||
| 16 | + | var m model.Model | |||
| 17 | + | if err := json.Unmarshal(b, &m); err != nil { | |||
| 18 | + | return nil, err | |||
| 19 | + | } | |||
| 20 | + | return &m, nil | |||
| 21 | + | } | |||
A cmd/pipewiregen/internal/parser/parser_test.go +16 -0
| @@ -0,0 +1,16 @@ | |||
| 1 | + | package parser | |
| 2 | + | ||
| 3 | + | import "testing" | |
| 4 | + | ||
| 5 | + | func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | |
| 6 | + | model, err := Load("testdata/minimal_pipewire.json") | |
| 7 | + | if err != nil { | |
| 8 | + | t.Fatalf("Load returned error: %v", err) | |
| 9 | + | } | |
| 10 | + | if got, want := len(model.Libraries), 1; got != want { | |
| 11 | + | t.Fatalf("libraries=%d want %d", got, want) | |
| 12 | + | } | |
| 13 | + | if got, want := model.Groups[0].Name, "init"; got != want { | |
| 14 | + | t.Fatalf("group=%q want %q", got, want) | |
| 15 | + | } | |
| 16 | + | } | |
| @@ -0,0 +1,16 @@ | |||||
| 1 | + | package parser | |||
| 2 | + | ||||
| 3 | + | import "testing" | |||
| 4 | + | ||||
| 5 | + | func TestLoadParsesLibraryAndSymbolGroups(t *testing.T) { | |||
| 6 | + | model, err := Load("testdata/minimal_pipewire.json") | |||
| 7 | + | if err != nil { | |||
| 8 | + | t.Fatalf("Load returned error: %v", err) | |||
| 9 | + | } | |||
| 10 | + | if got, want := len(model.Libraries), 1; got != want { | |||
| 11 | + | t.Fatalf("libraries=%d want %d", got, want) | |||
| 12 | + | } | |||
| 13 | + | if got, want := model.Groups[0].Name, "init"; got != want { | |||
| 14 | + | t.Fatalf("group=%q want %q", got, want) | |||
| 15 | + | } | |||
| 16 | + | } | |||
A cmd/pipewiregen/internal/parser/testdata/minimal_pipewire.json +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | { | |
| 2 | + | "libraries": [ | |
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | + | ], | |
| 5 | + | "groups": [ | |
| 6 | + | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | |
| 7 | + | ], | |
| 8 | + | "symbols": [ | |
| 9 | + | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | |
| 10 | + | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | |
| 11 | + | ] | |
| 12 | + | } | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | { | |||
| 2 | + | "libraries": [ | |||
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |||
| 4 | + | ], | |||
| 5 | + | "groups": [ | |||
| 6 | + | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | |||
| 7 | + | ], | |||
| 8 | + | "symbols": [ | |||
| 9 | + | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | |||
| 10 | + | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | |||
| 11 | + | ] | |||
| 12 | + | } | |||
A cmd/pipewiregen/internal/pipewiregen/normalize.go +3 -0
| @@ -0,0 +1,3 @@ | |||
| 1 | + | // Package pipewiregen contains PipeWire-specific naming and grouping rules | |
| 2 | + | // for the code generator. | |
| 3 | + | package pipewiregen | |
| @@ -0,0 +1,3 @@ | |||||
| 1 | + | // Package pipewiregen contains PipeWire-specific naming and grouping rules | |||
| 2 | + | // for the code generator. | |||
| 3 | + | package pipewiregen | |||
A cmd/pipewiregen/main.go +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | package main | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "os" | |
| 6 | + | "path/filepath" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/parser" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | func main() { | |
| 12 | + | root, err := os.Getwd() | |
| 13 | + | if err != nil { | |
| 14 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |
| 15 | + | os.Exit(1) | |
| 16 | + | } | |
| 17 | + | _, err = parser.Load(filepath.Join(root, "gen", "pipewire.json")) | |
| 18 | + | if err != nil { | |
| 19 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |
| 20 | + | os.Exit(1) | |
| 21 | + | } | |
| 22 | + | // Emit step will be added in Task 2. | |
| 23 | + | } | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | package main | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | "os" | |||
| 6 | + | "path/filepath" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/parser" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | func main() { | |||
| 12 | + | root, err := os.Getwd() | |||
| 13 | + | if err != nil { | |||
| 14 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |||
| 15 | + | os.Exit(1) | |||
| 16 | + | } | |||
| 17 | + | _, err = parser.Load(filepath.Join(root, "gen", "pipewire.json")) | |||
| 18 | + | if err != nil { | |||
| 19 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |||
| 20 | + | os.Exit(1) | |||
| 21 | + | } | |||
| 22 | + | // Emit step will be added in Task 2. | |||
| 23 | + | } | |||
A gen/pipewire.json +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | { | |
| 2 | + | "libraries": [ | |
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | + | ], | |
| 5 | + | "groups": [ | |
| 6 | + | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | |
| 7 | + | ], | |
| 8 | + | "symbols": [ | |
| 9 | + | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | |
| 10 | + | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | |
| 11 | + | ] | |
| 12 | + | } | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | { | |||
| 2 | + | "libraries": [ | |||
| 3 | + | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |||
| 4 | + | ], | |||
| 5 | + | "groups": [ | |||
| 6 | + | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | |||
| 7 | + | ], | |||
| 8 | + | "symbols": [ | |||
| 9 | + | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | |||
| 10 | + | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | |||
| 11 | + | ] | |||
| 12 | + | } | |||
A gen/spa.json +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | { | |
| 2 | + | "libraries": [ | |
| 3 | + | {"name": "spa", "soname": "libspa-0.2.so.0"} | |
| 4 | + | ], | |
| 5 | + | "groups": [], | |
| 6 | + | "symbols": [] | |
| 7 | + | } | |
| @@ -0,0 +1,7 @@ | |||||
| 1 | + | { | |||
| 2 | + | "libraries": [ | |||
| 3 | + | {"name": "spa", "soname": "libspa-0.2.so.0"} | |||
| 4 | + | ], | |||
| 5 | + | "groups": [], | |||
| 6 | + | "symbols": [] | |||
| 7 | + | } | |||
A generate.go +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | //go:generate go run ./cmd/pipewiregen | |
| 2 | + | //go:generate mockery | |
| 3 | + | ||
| 4 | + | package puregopipewire | |
| @@ -0,0 +1,4 @@ | |||||
| 1 | + | //go:generate go run ./cmd/pipewiregen | |||
| 2 | + | //go:generate mockery | |||
| 3 | + | ||||
| 4 | + | package puregopipewire | |||
A go.mod +3 -0
| @@ -0,0 +1,3 @@ | |||
| 1 | + | module github.com/bnema/purego-pipewire | |
| 2 | + | ||
| 3 | + | go 1.24.0 | |
| @@ -0,0 +1,3 @@ | |||||
| 1 | + | module github.com/bnema/purego-pipewire | |||
| 2 | + | ||||
| 3 | + | go 1.24.0 | |||