feat: generate first raw PipeWire bindings
- commit
- 768bff5fb26c49e4f2046ba7a053bc1071112054
- author
- brice · · 2026-04-09 05:23 UTC
- parent
- 9f72b60
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
9 files · +292 -7
M cmd/pipewiregen/internal/emitter/emitter.go +197 -0
| @@ -1,2 +1,199 @@ | |||
| 1 | 1 | // Package emitter renders generated Go source files from the binding model. | |
| 2 | 2 | package emitter | |
| 3 | + | ||
| 4 | + | import ( | |
| 5 | + | "bytes" | |
| 6 | + | "fmt" | |
| 7 | + | "os" | |
| 8 | + | "path/filepath" | |
| 9 | + | "strings" | |
| 10 | + | "text/template" | |
| 11 | + | ||
| 12 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // Emit generates Go source files from the model and writes them to the root directory. | |
| 16 | + | // It returns a map of generated file paths to their content. | |
| 17 | + | func Emit(m *model.Model, root string) (map[string][]byte, error) { | |
| 18 | + | out := make(map[string][]byte) | |
| 19 | + | for _, group := range m.Groups { | |
| 20 | + | capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go") | |
| 21 | + | portPath := filepath.Join("internal", "ports", "out", group.Name+"_gen.go") | |
| 22 | + | ||
| 23 | + | capiContent, err := renderCAPI(group, m.Symbols, m.Libraries) | |
| 24 | + | if err != nil { | |
| 25 | + | return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err) | |
| 26 | + | } | |
| 27 | + | out[capiPath] = capiContent | |
| 28 | + | ||
| 29 | + | portContent, err := renderPort(group, m.Symbols) | |
| 30 | + | if err != nil { | |
| 31 | + | return nil, fmt.Errorf("render port for group %q: %w", group.Name, err) | |
| 32 | + | } | |
| 33 | + | out[portPath] = portContent | |
| 34 | + | } | |
| 35 | + | return out, writeAll(root, out) | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // renderCAPI generates the raw C API function types and registration helpers. | |
| 39 | + | func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library) ([]byte, error) { | |
| 40 | + | // Find symbols belonging to this group | |
| 41 | + | groupSymbols := filterSymbolsByGroup(symbols, group.Name) | |
| 42 | + | ||
| 43 | + | // Build library map for lookup | |
| 44 | + | libMap := make(map[string]string) | |
| 45 | + | for _, lib := range libraries { | |
| 46 | + | libMap[lib.Name] = lib.SOName | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | data := struct { | |
| 50 | + | Group model.Group | |
| 51 | + | Symbols []model.Symbol | |
| 52 | + | LibraryMap map[string]string | |
| 53 | + | }{ | |
| 54 | + | Group: group, | |
| 55 | + | Symbols: groupSymbols, | |
| 56 | + | LibraryMap: libMap, | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | tmpl, err := template.New("capi").Funcs(template.FuncMap{ | |
| 60 | + | "title": strings.Title, | |
| 61 | + | }).Parse(capiTemplate) | |
| 62 | + | if err != nil { | |
| 63 | + | return nil, err | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | var buf bytes.Buffer | |
| 67 | + | if err := tmpl.Execute(&buf, data); err != nil { | |
| 68 | + | return nil, err | |
| 69 | + | } | |
| 70 | + | return buf.Bytes(), nil | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // renderPort generates the outbound interface definitions. | |
| 74 | + | func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |
| 75 | + | groupSymbols := filterSymbolsByGroup(symbols, group.Name) | |
| 76 | + | ||
| 77 | + | // Convert symbols to methods with proper Go names | |
| 78 | + | type Method struct { | |
| 79 | + | Name string | |
| 80 | + | GoName string | |
| 81 | + | Signature string | |
| 82 | + | CName string | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | methods := make([]Method, 0, len(groupSymbols)) | |
| 86 | + | for _, sym := range groupSymbols { | |
| 87 | + | methods = append(methods, Method{ | |
| 88 | + | Name: sym.Name, | |
| 89 | + | GoName: toGoName(sym.Name), | |
| 90 | + | Signature: sym.Signature, | |
| 91 | + | CName: sym.Name, | |
| 92 | + | }) | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | data := struct { | |
| 96 | + | Group model.Group | |
| 97 | + | Methods []Method | |
| 98 | + | }{ | |
| 99 | + | Group: group, | |
| 100 | + | Methods: methods, | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | tmpl, err := template.New("port").Funcs(template.FuncMap{ | |
| 104 | + | "title": strings.Title, | |
| 105 | + | }).Parse(portTemplate) | |
| 106 | + | if err != nil { | |
| 107 | + | return nil, err | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | var buf bytes.Buffer | |
| 111 | + | if err := tmpl.Execute(&buf, data); err != nil { | |
| 112 | + | return nil, err | |
| 113 | + | } | |
| 114 | + | return buf.Bytes(), nil | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // filterSymbolsByGroup returns symbols that belong to the given group. | |
| 118 | + | func filterSymbolsByGroup(symbols []model.Symbol, groupName string) []model.Symbol { | |
| 119 | + | var result []model.Symbol | |
| 120 | + | for _, sym := range symbols { | |
| 121 | + | if sym.Group == groupName { | |
| 122 | + | result = append(result, sym) | |
| 123 | + | } | |
| 124 | + | } | |
| 125 | + | return result | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // toGoName converts a C function name like pw_init to a Go name like PWInit. | |
| 129 | + | func toGoName(cName string) string { | |
| 130 | + | // Handle pw_ prefix specially | |
| 131 | + | if strings.HasPrefix(cName, "pw_") { | |
| 132 | + | name := cName[3:] | |
| 133 | + | parts := strings.Split(name, "_") | |
| 134 | + | for i, p := range parts { | |
| 135 | + | parts[i] = strings.Title(p) | |
| 136 | + | } | |
| 137 | + | return "PW" + strings.Join(parts, "") | |
| 138 | + | } | |
| 139 | + | // Generic conversion | |
| 140 | + | parts := strings.Split(cName, "_") | |
| 141 | + | for i, p := range parts { | |
| 142 | + | parts[i] = strings.Title(p) | |
| 143 | + | } | |
| 144 | + | return strings.Join(parts, "") | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // writeAll writes all generated files to disk. | |
| 148 | + | func writeAll(root string, files map[string][]byte) error { | |
| 149 | + | for path, content := range files { | |
| 150 | + | fullPath := filepath.Join(root, path) | |
| 151 | + | dir := filepath.Dir(fullPath) | |
| 152 | + | if err := os.MkdirAll(dir, 0755); err != nil { | |
| 153 | + | return fmt.Errorf("mkdir %s: %w", dir, err) | |
| 154 | + | } | |
| 155 | + | if err := os.WriteFile(fullPath, content, 0644); err != nil { | |
| 156 | + | return fmt.Errorf("write %s: %w", fullPath, err) | |
| 157 | + | } | |
| 158 | + | } | |
| 159 | + | return nil | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | const capiTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |
| 163 | + | ||
| 164 | + | package capi | |
| 165 | + | ||
| 166 | + | import "github.com/ebitengine/purego" | |
| 167 | + | ||
| 168 | + | {{range .Symbols}} | |
| 169 | + | // {{.Name}}Func is the function type for {{.Name}}. | |
| 170 | + | type {{.Name}}Func {{.Signature}} | |
| 171 | + | {{end}} | |
| 172 | + | ||
| 173 | + | var ( | |
| 174 | + | {{range .Symbols}} | |
| 175 | + | {{.Name}} {{.Name}}Func | |
| 176 | + | {{end}} | |
| 177 | + | ) | |
| 178 | + | ||
| 179 | + | func register{{.Group.Name | title}}(handle uintptr) error { | |
| 180 | + | {{range .Symbols}} | |
| 181 | + | if err := purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}"); err != nil { | |
| 182 | + | return err | |
| 183 | + | } | |
| 184 | + | {{end}} | |
| 185 | + | return nil | |
| 186 | + | } | |
| 187 | + | ` | |
| 188 | + | ||
| 189 | + | const portTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |
| 190 | + | ||
| 191 | + | package out | |
| 192 | + | ||
| 193 | + | // {{.Group.Interface}} defines the outbound interface for {{.Group.Name}} operations. | |
| 194 | + | type {{.Group.Interface}} interface { | |
| 195 | + | {{range .Methods}} | |
| 196 | + | {{.GoName}} {{.Signature}} | |
| 197 | + | {{end}} | |
| 198 | + | } | |
| 199 | + | ` | |
| @@ -1,2 +1,199 @@ | |||||
| 1 | // Package emitter renders generated Go source files from the binding model. | 1 | // Package emitter renders generated Go source files from the binding model. | ||
| 2 | package emitter | 2 | package emitter | ||
| 3 | + | ||||
| 4 | + | import ( | |||
| 5 | + | "bytes" | |||
| 6 | + | "fmt" | |||
| 7 | + | "os" | |||
| 8 | + | "path/filepath" | |||
| 9 | + | "strings" | |||
| 10 | + | "text/template" | |||
| 11 | + | ||||
| 12 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // Emit generates Go source files from the model and writes them to the root directory. | |||
| 16 | + | // It returns a map of generated file paths to their content. | |||
| 17 | + | func Emit(m *model.Model, root string) (map[string][]byte, error) { | |||
| 18 | + | out := make(map[string][]byte) | |||
| 19 | + | for _, group := range m.Groups { | |||
| 20 | + | capiPath := filepath.Join("internal", "capi", group.Name+"_gen.go") | |||
| 21 | + | portPath := filepath.Join("internal", "ports", "out", group.Name+"_gen.go") | |||
| 22 | + | ||||
| 23 | + | capiContent, err := renderCAPI(group, m.Symbols, m.Libraries) | |||
| 24 | + | if err != nil { | |||
| 25 | + | return nil, fmt.Errorf("render capi for group %q: %w", group.Name, err) | |||
| 26 | + | } | |||
| 27 | + | out[capiPath] = capiContent | |||
| 28 | + | ||||
| 29 | + | portContent, err := renderPort(group, m.Symbols) | |||
| 30 | + | if err != nil { | |||
| 31 | + | return nil, fmt.Errorf("render port for group %q: %w", group.Name, err) | |||
| 32 | + | } | |||
| 33 | + | out[portPath] = portContent | |||
| 34 | + | } | |||
| 35 | + | return out, writeAll(root, out) | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // renderCAPI generates the raw C API function types and registration helpers. | |||
| 39 | + | func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Library) ([]byte, error) { | |||
| 40 | + | // Find symbols belonging to this group | |||
| 41 | + | groupSymbols := filterSymbolsByGroup(symbols, group.Name) | |||
| 42 | + | ||||
| 43 | + | // Build library map for lookup | |||
| 44 | + | libMap := make(map[string]string) | |||
| 45 | + | for _, lib := range libraries { | |||
| 46 | + | libMap[lib.Name] = lib.SOName | |||
| 47 | + | } | |||
| 48 | + | ||||
| 49 | + | data := struct { | |||
| 50 | + | Group model.Group | |||
| 51 | + | Symbols []model.Symbol | |||
| 52 | + | LibraryMap map[string]string | |||
| 53 | + | }{ | |||
| 54 | + | Group: group, | |||
| 55 | + | Symbols: groupSymbols, | |||
| 56 | + | LibraryMap: libMap, | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | tmpl, err := template.New("capi").Funcs(template.FuncMap{ | |||
| 60 | + | "title": strings.Title, | |||
| 61 | + | }).Parse(capiTemplate) | |||
| 62 | + | if err != nil { | |||
| 63 | + | return nil, err | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | var buf bytes.Buffer | |||
| 67 | + | if err := tmpl.Execute(&buf, data); err != nil { | |||
| 68 | + | return nil, err | |||
| 69 | + | } | |||
| 70 | + | return buf.Bytes(), nil | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // renderPort generates the outbound interface definitions. | |||
| 74 | + | func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |||
| 75 | + | groupSymbols := filterSymbolsByGroup(symbols, group.Name) | |||
| 76 | + | ||||
| 77 | + | // Convert symbols to methods with proper Go names | |||
| 78 | + | type Method struct { | |||
| 79 | + | Name string | |||
| 80 | + | GoName string | |||
| 81 | + | Signature string | |||
| 82 | + | CName string | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | methods := make([]Method, 0, len(groupSymbols)) | |||
| 86 | + | for _, sym := range groupSymbols { | |||
| 87 | + | methods = append(methods, Method{ | |||
| 88 | + | Name: sym.Name, | |||
| 89 | + | GoName: toGoName(sym.Name), | |||
| 90 | + | Signature: sym.Signature, | |||
| 91 | + | CName: sym.Name, | |||
| 92 | + | }) | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | data := struct { | |||
| 96 | + | Group model.Group | |||
| 97 | + | Methods []Method | |||
| 98 | + | }{ | |||
| 99 | + | Group: group, | |||
| 100 | + | Methods: methods, | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | tmpl, err := template.New("port").Funcs(template.FuncMap{ | |||
| 104 | + | "title": strings.Title, | |||
| 105 | + | }).Parse(portTemplate) | |||
| 106 | + | if err != nil { | |||
| 107 | + | return nil, err | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | var buf bytes.Buffer | |||
| 111 | + | if err := tmpl.Execute(&buf, data); err != nil { | |||
| 112 | + | return nil, err | |||
| 113 | + | } | |||
| 114 | + | return buf.Bytes(), nil | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // filterSymbolsByGroup returns symbols that belong to the given group. | |||
| 118 | + | func filterSymbolsByGroup(symbols []model.Symbol, groupName string) []model.Symbol { | |||
| 119 | + | var result []model.Symbol | |||
| 120 | + | for _, sym := range symbols { | |||
| 121 | + | if sym.Group == groupName { | |||
| 122 | + | result = append(result, sym) | |||
| 123 | + | } | |||
| 124 | + | } | |||
| 125 | + | return result | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | // toGoName converts a C function name like pw_init to a Go name like PWInit. | |||
| 129 | + | func toGoName(cName string) string { | |||
| 130 | + | // Handle pw_ prefix specially | |||
| 131 | + | if strings.HasPrefix(cName, "pw_") { | |||
| 132 | + | name := cName[3:] | |||
| 133 | + | parts := strings.Split(name, "_") | |||
| 134 | + | for i, p := range parts { | |||
| 135 | + | parts[i] = strings.Title(p) | |||
| 136 | + | } | |||
| 137 | + | return "PW" + strings.Join(parts, "") | |||
| 138 | + | } | |||
| 139 | + | // Generic conversion | |||
| 140 | + | parts := strings.Split(cName, "_") | |||
| 141 | + | for i, p := range parts { | |||
| 142 | + | parts[i] = strings.Title(p) | |||
| 143 | + | } | |||
| 144 | + | return strings.Join(parts, "") | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // writeAll writes all generated files to disk. | |||
| 148 | + | func writeAll(root string, files map[string][]byte) error { | |||
| 149 | + | for path, content := range files { | |||
| 150 | + | fullPath := filepath.Join(root, path) | |||
| 151 | + | dir := filepath.Dir(fullPath) | |||
| 152 | + | if err := os.MkdirAll(dir, 0755); err != nil { | |||
| 153 | + | return fmt.Errorf("mkdir %s: %w", dir, err) | |||
| 154 | + | } | |||
| 155 | + | if err := os.WriteFile(fullPath, content, 0644); err != nil { | |||
| 156 | + | return fmt.Errorf("write %s: %w", fullPath, err) | |||
| 157 | + | } | |||
| 158 | + | } | |||
| 159 | + | return nil | |||
| 160 | + | } | |||
| 161 | + | ||||
| 162 | + | const capiTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||
| 163 | + | ||||
| 164 | + | package capi | |||
| 165 | + | ||||
| 166 | + | import "github.com/ebitengine/purego" | |||
| 167 | + | ||||
| 168 | + | {{range .Symbols}} | |||
| 169 | + | // {{.Name}}Func is the function type for {{.Name}}. | |||
| 170 | + | type {{.Name}}Func {{.Signature}} | |||
| 171 | + | {{end}} | |||
| 172 | + | ||||
| 173 | + | var ( | |||
| 174 | + | {{range .Symbols}} | |||
| 175 | + | {{.Name}} {{.Name}}Func | |||
| 176 | + | {{end}} | |||
| 177 | + | ) | |||
| 178 | + | ||||
| 179 | + | func register{{.Group.Name | title}}(handle uintptr) error { | |||
| 180 | + | {{range .Symbols}} | |||
| 181 | + | if err := purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}"); err != nil { | |||
| 182 | + | return err | |||
| 183 | + | } | |||
| 184 | + | {{end}} | |||
| 185 | + | return nil | |||
| 186 | + | } | |||
| 187 | + | ` | |||
| 188 | + | ||||
| 189 | + | const portTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||
| 190 | + | ||||
| 191 | + | package out | |||
| 192 | + | ||||
| 193 | + | // {{.Group.Interface}} defines the outbound interface for {{.Group.Name}} operations. | |||
| 194 | + | type {{.Group.Interface}} interface { | |||
| 195 | + | {{range .Methods}} | |||
| 196 | + | {{.GoName}} {{.Signature}} | |||
| 197 | + | {{end}} | |||
| 198 | + | } | |||
| 199 | + | ` | |||
M cmd/pipewiregen/internal/emitter/emitter_test.go +34 -0
| @@ -1,1 +1,35 @@ | |||
| 1 | 1 | package emitter | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func testModel() *model.Model { | |
| 10 | + | return &model.Model{ | |
| 11 | + | Libraries: []model.Library{ | |
| 12 | + | {Name: "pipewire", SOName: "libpipewire-0.3.so.0"}, | |
| 13 | + | }, | |
| 14 | + | Groups: []model.Group{ | |
| 15 | + | {Name: "init", Interface: "InitAPI", Package: "out", Symbols: []string{"pw_init", "pw_deinit"}}, | |
| 16 | + | }, | |
| 17 | + | Symbols: []model.Symbol{ | |
| 18 | + | {Name: "pw_init", Library: "pipewire", Group: "init", Signature: "func(argc *int32, argv ***byte)"}, | |
| 19 | + | {Name: "pw_deinit", Library: "pipewire", Group: "init", Signature: "func()"}, | |
| 20 | + | }, | |
| 21 | + | } | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func TestEmitWritesCAPIAndPortFiles(t *testing.T) { | |
| 25 | + | paths, err := Emit(testModel(), t.TempDir()) | |
| 26 | + | if err != nil { | |
| 27 | + | t.Fatalf("Emit returned error: %v", err) | |
| 28 | + | } | |
| 29 | + | if _, ok := paths["internal/capi/init_gen.go"]; !ok { | |
| 30 | + | t.Fatalf("missing capi output") | |
| 31 | + | } | |
| 32 | + | if _, ok := paths["internal/ports/out/init_gen.go"]; !ok { | |
| 33 | + | t.Fatalf("missing port output") | |
| 34 | + | } | |
| 35 | + | } | |
| @@ -1,1 +1,35 @@ | |||||
| 1 | package emitter | 1 | package emitter | ||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func testModel() *model.Model { | |||
| 10 | + | return &model.Model{ | |||
| 11 | + | Libraries: []model.Library{ | |||
| 12 | + | {Name: "pipewire", SOName: "libpipewire-0.3.so.0"}, | |||
| 13 | + | }, | |||
| 14 | + | Groups: []model.Group{ | |||
| 15 | + | {Name: "init", Interface: "InitAPI", Package: "out", Symbols: []string{"pw_init", "pw_deinit"}}, | |||
| 16 | + | }, | |||
| 17 | + | Symbols: []model.Symbol{ | |||
| 18 | + | {Name: "pw_init", Library: "pipewire", Group: "init", Signature: "func(argc *int32, argv ***byte)"}, | |||
| 19 | + | {Name: "pw_deinit", Library: "pipewire", Group: "init", Signature: "func()"}, | |||
| 20 | + | }, | |||
| 21 | + | } | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func TestEmitWritesCAPIAndPortFiles(t *testing.T) { | |||
| 25 | + | paths, err := Emit(testModel(), t.TempDir()) | |||
| 26 | + | if err != nil { | |||
| 27 | + | t.Fatalf("Emit returned error: %v", err) | |||
| 28 | + | } | |||
| 29 | + | if _, ok := paths["internal/capi/init_gen.go"]; !ok { | |||
| 30 | + | t.Fatalf("missing capi output") | |||
| 31 | + | } | |||
| 32 | + | if _, ok := paths["internal/ports/out/init_gen.go"]; !ok { | |||
| 33 | + | t.Fatalf("missing port output") | |||
| 34 | + | } | |||
| 35 | + | } | |||
M cmd/pipewiregen/internal/model/model.go +6 -2
| @@ -15,8 +15,10 @@ type Library struct { | |||
| 15 | 15 | ||
| 16 | 16 | // Group represents a logical grouping of symbols. | |
| 17 | 17 | type Group struct { | |
| 18 | - | Name string `json:"name"` | |
| 19 | - | Symbols []string `json:"symbols"` | |
| 18 | + | Name string `json:"name"` | |
| 19 | + | Symbols []string `json:"symbols"` | |
| 20 | + | Interface string `json:"interface"` | |
| 21 | + | Package string `json:"package"` | |
| 20 | 22 | } | |
| 21 | 23 | ||
| 22 | 24 | // Symbol represents a single C function to bind. | |
| @@ -24,4 +26,6 @@ type Symbol struct { | |||
| 24 | 26 | Name string `json:"name"` | |
| 25 | 27 | Library string `json:"library"` | |
| 26 | 28 | Signature string `json:"signature"` | |
| 29 | + | Optional bool `json:"optional,omitempty"` | |
| 30 | + | Group string `json:"group"` | |
| 27 | 31 | } | |
| @@ -15,8 +15,10 @@ type Library struct { | |||||
| 15 | 15 | ||||
| 16 | // Group represents a logical grouping of symbols. | 16 | // Group represents a logical grouping of symbols. | ||
| 17 | type Group struct { | 17 | type Group struct { | ||
| 18 | - | Name string `json:"name"` | 18 | + | Name string `json:"name"` |
| 19 | - | Symbols []string `json:"symbols"` | 19 | + | Symbols []string `json:"symbols"` |
| 20 | + | Interface string `json:"interface"` | |||
| 21 | + | Package string `json:"package"` | |||
| 20 | } | 22 | } | ||
| 21 | 23 | ||||
| 22 | // Symbol represents a single C function to bind. | 24 | // Symbol represents a single C function to bind. | ||
| @@ -24,4 +26,6 @@ type Symbol struct { | |||||
| 24 | Name string `json:"name"` | 26 | Name string `json:"name"` | ||
| 25 | Library string `json:"library"` | 27 | Library string `json:"library"` | ||
| 26 | Signature string `json:"signature"` | 28 | Signature string `json:"signature"` | ||
| 29 | + | Optional bool `json:"optional,omitempty"` | |||
| 30 | + | Group string `json:"group"` | |||
| 27 | } | 31 | } | ||
M cmd/pipewiregen/main.go +6 -2
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "os" | |
| 6 | 6 | "path/filepath" | |
| 7 | 7 | ||
| 8 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/emitter" | |
| 8 | 9 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/parser" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| @@ -14,10 +15,13 @@ func main() { | |||
| 14 | 15 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |
| 15 | 16 | os.Exit(1) | |
| 16 | 17 | } | |
| 17 | - | _, err = parser.Load(filepath.Join(root, "gen", "pipewire.json")) | |
| 18 | + | pipewireModel, err := parser.Load(filepath.Join(root, "gen", "pipewire.json")) | |
| 18 | 19 | if err != nil { | |
| 19 | 20 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |
| 20 | 21 | os.Exit(1) | |
| 21 | 22 | } | |
| 22 | - | // Emit step will be added in Task 2. | |
| 23 | + | if _, err := emitter.Emit(pipewireModel, root); err != nil { | |
| 24 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |
| 25 | + | os.Exit(1) | |
| 26 | + | } | |
| 23 | 27 | } | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "os" | 5 | "os" | ||
| 6 | "path/filepath" | 6 | "path/filepath" | ||
| 7 | 7 | ||||
| 8 | + | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/emitter" | |||
| 8 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/parser" | 9 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/parser" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| @@ -14,10 +15,13 @@ func main() { | |||||
| 14 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | 15 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | ||
| 15 | os.Exit(1) | 16 | os.Exit(1) | ||
| 16 | } | 17 | } | ||
| 17 | - | _, err = parser.Load(filepath.Join(root, "gen", "pipewire.json")) | 18 | + | pipewireModel, err := parser.Load(filepath.Join(root, "gen", "pipewire.json")) |
| 18 | if err != nil { | 19 | if err != nil { | ||
| 19 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | 20 | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | ||
| 20 | os.Exit(1) | 21 | os.Exit(1) | ||
| 21 | } | 22 | } | ||
| 22 | - | // Emit step will be added in Task 2. | 23 | + | if _, err := emitter.Emit(pipewireModel, root); err != nil { |
| 24 | + | fmt.Fprintf(os.Stderr, "pipewiregen: %v\n", err) | |||
| 25 | + | os.Exit(1) | |||
| 26 | + | } | |||
| 23 | } | 27 | } | ||
M gen/pipewire.json +3 -3
| @@ -3,10 +3,10 @@ | |||
| 3 | 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | 4 | ], | |
| 5 | 5 | "groups": [ | |
| 6 | - | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | |
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init", "pw_deinit"]} | |
| 7 | 7 | ], | |
| 8 | 8 | "symbols": [ | |
| 9 | - | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | |
| 10 | - | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | |
| 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"}, | |
| 10 | + | {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"} | |
| 11 | 11 | ] | |
| 12 | 12 | } | |
| @@ -3,10 +3,10 @@ | |||||
| 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | ||
| 4 | ], | 4 | ], | ||
| 5 | "groups": [ | 5 | "groups": [ | ||
| 6 | - | {"name": "init", "symbols": ["pw_init", "pw_deinit"]} | 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init", "pw_deinit"]} |
| 7 | ], | 7 | ], | ||
| 8 | "symbols": [ | 8 | "symbols": [ | ||
| 9 | - | {"name": "pw_init", "library": "pipewire", "signature": "func(argc *int32, argv ***byte)"}, | 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": "func(argc *int32, argv ***byte)"}, |
| 10 | - | {"name": "pw_deinit", "library": "pipewire", "signature": "func()"} | 10 | + | {"name": "pw_deinit", "library": "pipewire", "group": "init", "signature": "func()"} |
| 11 | ] | 11 | ] | ||
| 12 | } | 12 | } | ||
A internal/capi/.gitkeep +0 -0
no textual change.
A internal/capi/init_gen.go +34 -0
| @@ -0,0 +1,34 @@ | |||
| 1 | + | // Code generated by pipewiregen. DO NOT EDIT. | |
| 2 | + | ||
| 3 | + | package capi | |
| 4 | + | ||
| 5 | + | import "github.com/ebitengine/purego" | |
| 6 | + | ||
| 7 | + | ||
| 8 | + | // pw_initFunc is the function type for pw_init. | |
| 9 | + | type pw_initFunc func(argc *int32, argv ***byte) | |
| 10 | + | ||
| 11 | + | // pw_deinitFunc is the function type for pw_deinit. | |
| 12 | + | type pw_deinitFunc func() | |
| 13 | + | ||
| 14 | + | ||
| 15 | + | var ( | |
| 16 | + | ||
| 17 | + | pw_init pw_initFunc | |
| 18 | + | ||
| 19 | + | pw_deinit pw_deinitFunc | |
| 20 | + | ||
| 21 | + | ) | |
| 22 | + | ||
| 23 | + | func registerInit(handle uintptr) error { | |
| 24 | + | ||
| 25 | + | if err := purego.RegisterLibFunc(&pw_init, handle, "pw_init"); err != nil { | |
| 26 | + | return err | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | if err := purego.RegisterLibFunc(&pw_deinit, handle, "pw_deinit"); err != nil { | |
| 30 | + | return err | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | return nil | |
| 34 | + | } | |
| @@ -0,0 +1,34 @@ | |||||
| 1 | + | // Code generated by pipewiregen. DO NOT EDIT. | |||
| 2 | + | ||||
| 3 | + | package capi | |||
| 4 | + | ||||
| 5 | + | import "github.com/ebitengine/purego" | |||
| 6 | + | ||||
| 7 | + | ||||
| 8 | + | // pw_initFunc is the function type for pw_init. | |||
| 9 | + | type pw_initFunc func(argc *int32, argv ***byte) | |||
| 10 | + | ||||
| 11 | + | // pw_deinitFunc is the function type for pw_deinit. | |||
| 12 | + | type pw_deinitFunc func() | |||
| 13 | + | ||||
| 14 | + | ||||
| 15 | + | var ( | |||
| 16 | + | ||||
| 17 | + | pw_init pw_initFunc | |||
| 18 | + | ||||
| 19 | + | pw_deinit pw_deinitFunc | |||
| 20 | + | ||||
| 21 | + | ) | |||
| 22 | + | ||||
| 23 | + | func registerInit(handle uintptr) error { | |||
| 24 | + | ||||
| 25 | + | if err := purego.RegisterLibFunc(&pw_init, handle, "pw_init"); err != nil { | |||
| 26 | + | return err | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | if err := purego.RegisterLibFunc(&pw_deinit, handle, "pw_deinit"); err != nil { | |||
| 30 | + | return err | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | return nil | |||
| 34 | + | } | |||
A internal/ports/out/.gitkeep +0 -0
no textual change.