Task 2 review fixes: interface methods, purego dep, error context, templates
- commit
- f77114756f911bb9a6a71ea5a0edced65466a99c
- author
- brice · · 2026-04-09 05:32 UTC
- parent
- 768bff5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
11 files · +129 -77
M .mockery.yml +7 -16
| @@ -1,21 +1,12 @@ | |||
| 1 | - | with-expecter: true | |
| 1 | + | all: false | |
| 2 | + | template: testify | |
| 3 | + | formatter: goimports | |
| 2 | 4 | dir: "{{.InterfaceDir}}/mocks" | |
| 3 | - | outpkg: mocks | |
| 5 | + | pkgname: mocks | |
| 6 | + | filename: "mock_{{.InterfaceName}}.go" | |
| 7 | + | structname: "Mock{{.InterfaceName}}" | |
| 8 | + | ||
| 4 | 9 | packages: | |
| 5 | 10 | github.com/bnema/purego-pipewire/internal/ports/out: | |
| 6 | 11 | interfaces: | |
| 7 | - | CAPI: | |
| 8 | 12 | 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: | |
| @@ -1,21 +1,12 @@ | |||||
| 1 | - | with-expecter: true | 1 | + | all: false |
| 2 | + | template: testify | |||
| 3 | + | formatter: goimports | |||
| 2 | dir: "{{.InterfaceDir}}/mocks" | 4 | dir: "{{.InterfaceDir}}/mocks" | ||
| 3 | - | outpkg: mocks | 5 | + | pkgname: mocks |
| 6 | + | filename: "mock_{{.InterfaceName}}.go" | |||
| 7 | + | structname: "Mock{{.InterfaceName}}" | |||
| 8 | + | ||||
| 4 | packages: | 9 | packages: | ||
| 5 | github.com/bnema/purego-pipewire/internal/ports/out: | 10 | github.com/bnema/purego-pipewire/internal/ports/out: | ||
| 6 | interfaces: | 11 | interfaces: | ||
| 7 | - | CAPI: | |||
| 8 | InitAPI: | 12 | 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: | |||
M cmd/pipewiregen/internal/emitter/emitter.go +92 -37
| @@ -8,10 +8,40 @@ import ( | |||
| 8 | 8 | "path/filepath" | |
| 9 | 9 | "strings" | |
| 10 | 10 | "text/template" | |
| 11 | + | "unicode" | |
| 11 | 12 | ||
| 12 | 13 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| 16 | + | var ( | |
| 17 | + | capiTmpl *template.Template | |
| 18 | + | portTmpl *template.Template | |
| 19 | + | ) | |
| 20 | + | ||
| 21 | + | func init() { | |
| 22 | + | capiTmpl = template.Must(template.New("capi").Funcs(template.FuncMap{ | |
| 23 | + | "title": titleCase, | |
| 24 | + | }).Parse(capiTemplate)) | |
| 25 | + | ||
| 26 | + | portTmpl = template.Must(template.New("port").Funcs(template.FuncMap{ | |
| 27 | + | "title": titleCase, | |
| 28 | + | }).Parse(portTemplate)) | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | // titleCase converts a string to title case (first letter uppercase, rest lowercase). | |
| 32 | + | // Replaces deprecated strings.Title. | |
| 33 | + | func titleCase(s string) string { | |
| 34 | + | if s == "" { | |
| 35 | + | return "" | |
| 36 | + | } | |
| 37 | + | runes := []rune(s) | |
| 38 | + | runes[0] = unicode.ToUpper(runes[0]) | |
| 39 | + | for i := 1; i < len(runes); i++ { | |
| 40 | + | runes[i] = unicode.ToLower(runes[i]) | |
| 41 | + | } | |
| 42 | + | return string(runes) | |
| 43 | + | } | |
| 44 | + | ||
| 15 | 45 | // Emit generates Go source files from the model and writes them to the root directory. | |
| 16 | 46 | // It returns a map of generated file paths to their content. | |
| 17 | 47 | func Emit(m *model.Model, root string) (map[string][]byte, error) { | |
| @@ -56,16 +86,9 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib | |||
| 56 | 86 | LibraryMap: libMap, | |
| 57 | 87 | } | |
| 58 | 88 | ||
| 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 | 89 | var buf bytes.Buffer | |
| 67 | - | if err := tmpl.Execute(&buf, data); err != nil { | |
| 68 | - | return nil, err | |
| 90 | + | if err := capiTmpl.Execute(&buf, data); err != nil { | |
| 91 | + | return nil, fmt.Errorf("execute capi template for group %q: %w", group.Name, err) | |
| 69 | 92 | } | |
| 70 | 93 | return buf.Bytes(), nil | |
| 71 | 94 | } | |
| @@ -76,19 +99,22 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |||
| 76 | 99 | ||
| 77 | 100 | // Convert symbols to methods with proper Go names | |
| 78 | 101 | type Method struct { | |
| 79 | - | Name string | |
| 80 | - | GoName string | |
| 81 | - | Signature string | |
| 82 | - | CName string | |
| 102 | + | Name string | |
| 103 | + | GoName string | |
| 104 | + | Params string | |
| 105 | + | Results string | |
| 106 | + | CName string | |
| 83 | 107 | } | |
| 84 | 108 | ||
| 85 | 109 | methods := make([]Method, 0, len(groupSymbols)) | |
| 86 | 110 | for _, sym := range groupSymbols { | |
| 111 | + | params, results := parseSignature(sym.Signature) | |
| 87 | 112 | methods = append(methods, Method{ | |
| 88 | - | Name: sym.Name, | |
| 89 | - | GoName: toGoName(sym.Name), | |
| 90 | - | Signature: sym.Signature, | |
| 91 | - | CName: sym.Name, | |
| 113 | + | Name: sym.Name, | |
| 114 | + | GoName: toGoName(sym.Name), | |
| 115 | + | Params: params, | |
| 116 | + | Results: results, | |
| 117 | + | CName: sym.Name, | |
| 92 | 118 | }) | |
| 93 | 119 | } | |
| 94 | 120 | ||
| @@ -100,20 +126,53 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |||
| 100 | 126 | Methods: methods, | |
| 101 | 127 | } | |
| 102 | 128 | ||
| 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 | 129 | var buf bytes.Buffer | |
| 111 | - | if err := tmpl.Execute(&buf, data); err != nil { | |
| 112 | - | return nil, err | |
| 130 | + | if err := portTmpl.Execute(&buf, data); err != nil { | |
| 131 | + | return nil, fmt.Errorf("execute port template for group %q: %w", group.Name, err) | |
| 113 | 132 | } | |
| 114 | 133 | return buf.Bytes(), nil | |
| 115 | 134 | } | |
| 116 | 135 | ||
| 136 | + | // parseSignature splits a func signature like "func(argc *int32, argv ***byte)" into params and results. | |
| 137 | + | // Returns ("(argc *int32, argv ***byte)", "") for func with no returns, | |
| 138 | + | // or ("(x int)", "(int, error)") for func with returns. | |
| 139 | + | func parseSignature(sig string) (params, results string) { | |
| 140 | + | // Remove "func" prefix | |
| 141 | + | if !strings.HasPrefix(sig, "func") { | |
| 142 | + | return "()", "" | |
| 143 | + | } | |
| 144 | + | body := strings.TrimSpace(sig[4:]) | |
| 145 | + | ||
| 146 | + | // Find the closing paren of params (handles nested parens) | |
| 147 | + | depth := 0 | |
| 148 | + | paramsEnd := -1 | |
| 149 | + | for i, ch := range body { | |
| 150 | + | if ch == '(' { | |
| 151 | + | depth++ | |
| 152 | + | } else if ch == ')' { | |
| 153 | + | depth-- | |
| 154 | + | if depth == 0 { | |
| 155 | + | paramsEnd = i | |
| 156 | + | break | |
| 157 | + | } | |
| 158 | + | } | |
| 159 | + | } | |
| 160 | + | if paramsEnd < 0 { | |
| 161 | + | return "()", "" | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | params = body[:paramsEnd+1] | |
| 165 | + | rest := strings.TrimSpace(body[paramsEnd+1:]) | |
| 166 | + | ||
| 167 | + | // Check for return values | |
| 168 | + | if rest != "" { | |
| 169 | + | // Returns could be "error" or "(int, error)" or "int" | |
| 170 | + | results = rest | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | return params, results | |
| 174 | + | } | |
| 175 | + | ||
| 117 | 176 | // filterSymbolsByGroup returns symbols that belong to the given group. | |
| 118 | 177 | func filterSymbolsByGroup(symbols []model.Symbol, groupName string) []model.Symbol { | |
| 119 | 178 | var result []model.Symbol | |
| @@ -132,14 +191,14 @@ func toGoName(cName string) string { | |||
| 132 | 191 | name := cName[3:] | |
| 133 | 192 | parts := strings.Split(name, "_") | |
| 134 | 193 | for i, p := range parts { | |
| 135 | - | parts[i] = strings.Title(p) | |
| 194 | + | parts[i] = titleCase(p) | |
| 136 | 195 | } | |
| 137 | 196 | return "PW" + strings.Join(parts, "") | |
| 138 | 197 | } | |
| 139 | 198 | // Generic conversion | |
| 140 | 199 | parts := strings.Split(cName, "_") | |
| 141 | 200 | for i, p := range parts { | |
| 142 | - | parts[i] = strings.Title(p) | |
| 201 | + | parts[i] = titleCase(p) | |
| 143 | 202 | } | |
| 144 | 203 | return strings.Join(parts, "") | |
| 145 | 204 | } | |
| @@ -164,7 +223,6 @@ const capiTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||
| 164 | 223 | package capi | |
| 165 | 224 | ||
| 166 | 225 | import "github.com/ebitengine/purego" | |
| 167 | - | ||
| 168 | 226 | {{range .Symbols}} | |
| 169 | 227 | // {{.Name}}Func is the function type for {{.Name}}. | |
| 170 | 228 | type {{.Name}}Func {{.Signature}} | |
| @@ -176,13 +234,10 @@ var ( | |||
| 176 | 234 | {{end}} | |
| 177 | 235 | ) | |
| 178 | 236 | ||
| 179 | - | func register{{.Group.Name | title}}(handle uintptr) error { | |
| 237 | + | func register{{.Group.Name | title}}(handle uintptr) { | |
| 180 | 238 | {{range .Symbols}} | |
| 181 | - | if err := purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}"); err != nil { | |
| 182 | - | return err | |
| 183 | - | } | |
| 239 | + | purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}") | |
| 184 | 240 | {{end}} | |
| 185 | - | return nil | |
| 186 | 241 | } | |
| 187 | 242 | ` | |
| 188 | 243 | ||
| @@ -192,8 +247,8 @@ package out | |||
| 192 | 247 | ||
| 193 | 248 | // {{.Group.Interface}} defines the outbound interface for {{.Group.Name}} operations. | |
| 194 | 249 | type {{.Group.Interface}} interface { | |
| 195 | - | {{range .Methods}} | |
| 196 | - | {{.GoName}} {{.Signature}} | |
| 197 | - | {{end}} | |
| 250 | + | {{- range .Methods}} | |
| 251 | + | {{.GoName}}{{.Params}}{{if .Results}} {{.Results}}{{end}} | |
| 252 | + | {{- end}} | |
| 198 | 253 | } | |
| 199 | 254 | ` | |
| @@ -8,10 +8,40 @@ import ( | |||||
| 8 | "path/filepath" | 8 | "path/filepath" | ||
| 9 | "strings" | 9 | "strings" | ||
| 10 | "text/template" | 10 | "text/template" | ||
| 11 | + | "unicode" | |||
| 11 | 12 | ||||
| 12 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | 13 | "github.com/bnema/purego-pipewire/cmd/pipewiregen/internal/model" | ||
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| 16 | + | var ( | |||
| 17 | + | capiTmpl *template.Template | |||
| 18 | + | portTmpl *template.Template | |||
| 19 | + | ) | |||
| 20 | + | ||||
| 21 | + | func init() { | |||
| 22 | + | capiTmpl = template.Must(template.New("capi").Funcs(template.FuncMap{ | |||
| 23 | + | "title": titleCase, | |||
| 24 | + | }).Parse(capiTemplate)) | |||
| 25 | + | ||||
| 26 | + | portTmpl = template.Must(template.New("port").Funcs(template.FuncMap{ | |||
| 27 | + | "title": titleCase, | |||
| 28 | + | }).Parse(portTemplate)) | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | // titleCase converts a string to title case (first letter uppercase, rest lowercase). | |||
| 32 | + | // Replaces deprecated strings.Title. | |||
| 33 | + | func titleCase(s string) string { | |||
| 34 | + | if s == "" { | |||
| 35 | + | return "" | |||
| 36 | + | } | |||
| 37 | + | runes := []rune(s) | |||
| 38 | + | runes[0] = unicode.ToUpper(runes[0]) | |||
| 39 | + | for i := 1; i < len(runes); i++ { | |||
| 40 | + | runes[i] = unicode.ToLower(runes[i]) | |||
| 41 | + | } | |||
| 42 | + | return string(runes) | |||
| 43 | + | } | |||
| 44 | + | ||||
| 15 | // Emit generates Go source files from the model and writes them to the root directory. | 45 | // 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. | 46 | // It returns a map of generated file paths to their content. | ||
| 17 | func Emit(m *model.Model, root string) (map[string][]byte, error) { | 47 | func Emit(m *model.Model, root string) (map[string][]byte, error) { | ||
| @@ -56,16 +86,9 @@ func renderCAPI(group model.Group, symbols []model.Symbol, libraries []model.Lib | |||||
| 56 | LibraryMap: libMap, | 86 | LibraryMap: libMap, | ||
| 57 | } | 87 | } | ||
| 58 | 88 | ||||
| 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 | 89 | var buf bytes.Buffer | ||
| 67 | - | if err := tmpl.Execute(&buf, data); err != nil { | 90 | + | if err := capiTmpl.Execute(&buf, data); err != nil { |
| 68 | - | return nil, err | 91 | + | return nil, fmt.Errorf("execute capi template for group %q: %w", group.Name, err) |
| 69 | } | 92 | } | ||
| 70 | return buf.Bytes(), nil | 93 | return buf.Bytes(), nil | ||
| 71 | } | 94 | } | ||
| @@ -76,19 +99,22 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |||||
| 76 | 99 | ||||
| 77 | // Convert symbols to methods with proper Go names | 100 | // Convert symbols to methods with proper Go names | ||
| 78 | type Method struct { | 101 | type Method struct { | ||
| 79 | - | Name string | 102 | + | Name string |
| 80 | - | GoName string | 103 | + | GoName string |
| 81 | - | Signature string | 104 | + | Params string |
| 82 | - | CName string | 105 | + | Results string |
| 106 | + | CName string | |||
| 83 | } | 107 | } | ||
| 84 | 108 | ||||
| 85 | methods := make([]Method, 0, len(groupSymbols)) | 109 | methods := make([]Method, 0, len(groupSymbols)) | ||
| 86 | for _, sym := range groupSymbols { | 110 | for _, sym := range groupSymbols { | ||
| 111 | + | params, results := parseSignature(sym.Signature) | |||
| 87 | methods = append(methods, Method{ | 112 | methods = append(methods, Method{ | ||
| 88 | - | Name: sym.Name, | 113 | + | Name: sym.Name, |
| 89 | - | GoName: toGoName(sym.Name), | 114 | + | GoName: toGoName(sym.Name), |
| 90 | - | Signature: sym.Signature, | 115 | + | Params: params, |
| 91 | - | CName: sym.Name, | 116 | + | Results: results, |
| 117 | + | CName: sym.Name, | |||
| 92 | }) | 118 | }) | ||
| 93 | } | 119 | } | ||
| 94 | 120 | ||||
| @@ -100,20 +126,53 @@ func renderPort(group model.Group, symbols []model.Symbol) ([]byte, error) { | |||||
| 100 | Methods: methods, | 126 | Methods: methods, | ||
| 101 | } | 127 | } | ||
| 102 | 128 | ||||
| 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 | 129 | var buf bytes.Buffer | ||
| 111 | - | if err := tmpl.Execute(&buf, data); err != nil { | 130 | + | if err := portTmpl.Execute(&buf, data); err != nil { |
| 112 | - | return nil, err | 131 | + | return nil, fmt.Errorf("execute port template for group %q: %w", group.Name, err) |
| 113 | } | 132 | } | ||
| 114 | return buf.Bytes(), nil | 133 | return buf.Bytes(), nil | ||
| 115 | } | 134 | } | ||
| 116 | 135 | ||||
| 136 | + | // parseSignature splits a func signature like "func(argc *int32, argv ***byte)" into params and results. | |||
| 137 | + | // Returns ("(argc *int32, argv ***byte)", "") for func with no returns, | |||
| 138 | + | // or ("(x int)", "(int, error)") for func with returns. | |||
| 139 | + | func parseSignature(sig string) (params, results string) { | |||
| 140 | + | // Remove "func" prefix | |||
| 141 | + | if !strings.HasPrefix(sig, "func") { | |||
| 142 | + | return "()", "" | |||
| 143 | + | } | |||
| 144 | + | body := strings.TrimSpace(sig[4:]) | |||
| 145 | + | ||||
| 146 | + | // Find the closing paren of params (handles nested parens) | |||
| 147 | + | depth := 0 | |||
| 148 | + | paramsEnd := -1 | |||
| 149 | + | for i, ch := range body { | |||
| 150 | + | if ch == '(' { | |||
| 151 | + | depth++ | |||
| 152 | + | } else if ch == ')' { | |||
| 153 | + | depth-- | |||
| 154 | + | if depth == 0 { | |||
| 155 | + | paramsEnd = i | |||
| 156 | + | break | |||
| 157 | + | } | |||
| 158 | + | } | |||
| 159 | + | } | |||
| 160 | + | if paramsEnd < 0 { | |||
| 161 | + | return "()", "" | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | params = body[:paramsEnd+1] | |||
| 165 | + | rest := strings.TrimSpace(body[paramsEnd+1:]) | |||
| 166 | + | ||||
| 167 | + | // Check for return values | |||
| 168 | + | if rest != "" { | |||
| 169 | + | // Returns could be "error" or "(int, error)" or "int" | |||
| 170 | + | results = rest | |||
| 171 | + | } | |||
| 172 | + | ||||
| 173 | + | return params, results | |||
| 174 | + | } | |||
| 175 | + | ||||
| 117 | // filterSymbolsByGroup returns symbols that belong to the given group. | 176 | // filterSymbolsByGroup returns symbols that belong to the given group. | ||
| 118 | func filterSymbolsByGroup(symbols []model.Symbol, groupName string) []model.Symbol { | 177 | func filterSymbolsByGroup(symbols []model.Symbol, groupName string) []model.Symbol { | ||
| 119 | var result []model.Symbol | 178 | var result []model.Symbol | ||
| @@ -132,14 +191,14 @@ func toGoName(cName string) string { | |||||
| 132 | name := cName[3:] | 191 | name := cName[3:] | ||
| 133 | parts := strings.Split(name, "_") | 192 | parts := strings.Split(name, "_") | ||
| 134 | for i, p := range parts { | 193 | for i, p := range parts { | ||
| 135 | - | parts[i] = strings.Title(p) | 194 | + | parts[i] = titleCase(p) |
| 136 | } | 195 | } | ||
| 137 | return "PW" + strings.Join(parts, "") | 196 | return "PW" + strings.Join(parts, "") | ||
| 138 | } | 197 | } | ||
| 139 | // Generic conversion | 198 | // Generic conversion | ||
| 140 | parts := strings.Split(cName, "_") | 199 | parts := strings.Split(cName, "_") | ||
| 141 | for i, p := range parts { | 200 | for i, p := range parts { | ||
| 142 | - | parts[i] = strings.Title(p) | 201 | + | parts[i] = titleCase(p) |
| 143 | } | 202 | } | ||
| 144 | return strings.Join(parts, "") | 203 | return strings.Join(parts, "") | ||
| 145 | } | 204 | } | ||
| @@ -164,7 +223,6 @@ const capiTemplate = `// Code generated by pipewiregen. DO NOT EDIT. | |||||
| 164 | package capi | 223 | package capi | ||
| 165 | 224 | ||||
| 166 | import "github.com/ebitengine/purego" | 225 | import "github.com/ebitengine/purego" | ||
| 167 | - | ||||
| 168 | {{range .Symbols}} | 226 | {{range .Symbols}} | ||
| 169 | // {{.Name}}Func is the function type for {{.Name}}. | 227 | // {{.Name}}Func is the function type for {{.Name}}. | ||
| 170 | type {{.Name}}Func {{.Signature}} | 228 | type {{.Name}}Func {{.Signature}} | ||
| @@ -176,13 +234,10 @@ var ( | |||||
| 176 | {{end}} | 234 | {{end}} | ||
| 177 | ) | 235 | ) | ||
| 178 | 236 | ||||
| 179 | - | func register{{.Group.Name | title}}(handle uintptr) error { | 237 | + | func register{{.Group.Name | title}}(handle uintptr) { |
| 180 | {{range .Symbols}} | 238 | {{range .Symbols}} | ||
| 181 | - | if err := purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}"); err != nil { | 239 | + | purego.RegisterLibFunc(&{{.Name}}, handle, "{{.Name}}") |
| 182 | - | return err | |||
| 183 | - | } | |||
| 184 | {{end}} | 240 | {{end}} | ||
| 185 | - | return nil | |||
| 186 | } | 241 | } | ||
| 187 | ` | 242 | ` | ||
| 188 | 243 | ||||
| @@ -192,8 +247,8 @@ package out | |||||
| 192 | 247 | ||||
| 193 | // {{.Group.Interface}} defines the outbound interface for {{.Group.Name}} operations. | 248 | // {{.Group.Interface}} defines the outbound interface for {{.Group.Name}} operations. | ||
| 194 | type {{.Group.Interface}} interface { | 249 | type {{.Group.Interface}} interface { | ||
| 195 | - | {{range .Methods}} | 250 | + | {{- range .Methods}} |
| 196 | - | {{.GoName}} {{.Signature}} | 251 | + | {{.GoName}}{{.Params}}{{if .Results}} {{.Results}}{{end}} |
| 197 | - | {{end}} | 252 | + | {{- end}} |
| 198 | } | 253 | } | ||
| 199 | ` | 254 | ` | ||
M cmd/pipewiregen/internal/parser/parser.go +12 -1
| @@ -37,11 +37,16 @@ func validate(m *model.Model) error { | |||
| 37 | 37 | libNames[lib.Name] = true | |
| 38 | 38 | } | |
| 39 | 39 | ||
| 40 | - | // Validate groups | |
| 40 | + | // Build set of valid group names | |
| 41 | + | groupNames := make(map[string]bool) | |
| 41 | 42 | for i, group := range m.Groups { | |
| 42 | 43 | if group.Name == "" { | |
| 43 | 44 | return fmt.Errorf("group at index %d has empty name", i) | |
| 44 | 45 | } | |
| 46 | + | if group.Interface == "" { | |
| 47 | + | return fmt.Errorf("group %q has empty interface", group.Name) | |
| 48 | + | } | |
| 49 | + | groupNames[group.Name] = true | |
| 45 | 50 | } | |
| 46 | 51 | ||
| 47 | 52 | // Validate symbols | |
| @@ -58,6 +63,12 @@ func validate(m *model.Model) error { | |||
| 58 | 63 | if !libNames[sym.Library] { | |
| 59 | 64 | return fmt.Errorf("symbol %q references unknown library %q", sym.Name, sym.Library) | |
| 60 | 65 | } | |
| 66 | + | if sym.Group == "" { | |
| 67 | + | return fmt.Errorf("symbol %q has empty group", sym.Name) | |
| 68 | + | } | |
| 69 | + | if !groupNames[sym.Group] { | |
| 70 | + | return fmt.Errorf("symbol %q references unknown group %q", sym.Name, sym.Group) | |
| 71 | + | } | |
| 61 | 72 | } | |
| 62 | 73 | ||
| 63 | 74 | return nil | |
| @@ -37,11 +37,16 @@ func validate(m *model.Model) error { | |||||
| 37 | libNames[lib.Name] = true | 37 | libNames[lib.Name] = true | ||
| 38 | } | 38 | } | ||
| 39 | 39 | ||||
| 40 | - | // Validate groups | 40 | + | // Build set of valid group names |
| 41 | + | groupNames := make(map[string]bool) | |||
| 41 | for i, group := range m.Groups { | 42 | for i, group := range m.Groups { | ||
| 42 | if group.Name == "" { | 43 | if group.Name == "" { | ||
| 43 | return fmt.Errorf("group at index %d has empty name", i) | 44 | return fmt.Errorf("group at index %d has empty name", i) | ||
| 44 | } | 45 | } | ||
| 46 | + | if group.Interface == "" { | |||
| 47 | + | return fmt.Errorf("group %q has empty interface", group.Name) | |||
| 48 | + | } | |||
| 49 | + | groupNames[group.Name] = true | |||
| 45 | } | 50 | } | ||
| 46 | 51 | ||||
| 47 | // Validate symbols | 52 | // Validate symbols | ||
| @@ -58,6 +63,12 @@ func validate(m *model.Model) error { | |||||
| 58 | if !libNames[sym.Library] { | 63 | if !libNames[sym.Library] { | ||
| 59 | return fmt.Errorf("symbol %q references unknown library %q", sym.Name, sym.Library) | 64 | return fmt.Errorf("symbol %q references unknown library %q", sym.Name, sym.Library) | ||
| 60 | } | 65 | } | ||
| 66 | + | if sym.Group == "" { | |||
| 67 | + | return fmt.Errorf("symbol %q has empty group", sym.Name) | |||
| 68 | + | } | |||
| 69 | + | if !groupNames[sym.Group] { | |||
| 70 | + | return fmt.Errorf("symbol %q references unknown group %q", sym.Name, sym.Group) | |||
| 71 | + | } | |||
| 61 | } | 72 | } | ||
| 62 | 73 | ||||
| 63 | return nil | 74 | return nil | ||
M cmd/pipewiregen/internal/parser/testdata/empty_symbol_name.json +2 -2
| @@ -3,9 +3,9 @@ | |||
| 3 | 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | 4 | ], | |
| 5 | 5 | "groups": [ | |
| 6 | - | {"name": "init", "symbols": [""]} | |
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": [""]} | |
| 7 | 7 | ], | |
| 8 | 8 | "symbols": [ | |
| 9 | - | {"name": "", "library": "pipewire", "signature": "func()"} | |
| 9 | + | {"name": "", "library": "pipewire", "group": "init", "signature": "func()"} | |
| 10 | 10 | ] | |
| 11 | 11 | } | |
| @@ -3,9 +3,9 @@ | |||||
| 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": [""]} | 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": [""]} |
| 7 | ], | 7 | ], | ||
| 8 | "symbols": [ | 8 | "symbols": [ | ||
| 9 | - | {"name": "", "library": "pipewire", "signature": "func()"} | 9 | + | {"name": "", "library": "pipewire", "group": "init", "signature": "func()"} |
| 10 | ] | 10 | ] | ||
| 11 | } | 11 | } | ||
M cmd/pipewiregen/internal/parser/testdata/empty_symbol_sig.json +2 -2
| @@ -3,9 +3,9 @@ | |||
| 3 | 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | 4 | ], | |
| 5 | 5 | "groups": [ | |
| 6 | - | {"name": "init", "symbols": ["pw_init"]} | |
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} | |
| 7 | 7 | ], | |
| 8 | 8 | "symbols": [ | |
| 9 | - | {"name": "pw_init", "library": "pipewire", "signature": ""} | |
| 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": ""} | |
| 10 | 10 | ] | |
| 11 | 11 | } | |
| @@ -3,9 +3,9 @@ | |||||
| 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"]} | 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} |
| 7 | ], | 7 | ], | ||
| 8 | "symbols": [ | 8 | "symbols": [ | ||
| 9 | - | {"name": "pw_init", "library": "pipewire", "signature": ""} | 9 | + | {"name": "pw_init", "library": "pipewire", "group": "init", "signature": ""} |
| 10 | ] | 10 | ] | ||
| 11 | } | 11 | } | ||
M cmd/pipewiregen/internal/parser/testdata/minimal_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 | } | ||
M cmd/pipewiregen/internal/parser/testdata/unknown_library.json +2 -2
| @@ -3,9 +3,9 @@ | |||
| 3 | 3 | {"name": "pipewire", "soname": "libpipewire-0.3.so.0"} | |
| 4 | 4 | ], | |
| 5 | 5 | "groups": [ | |
| 6 | - | {"name": "init", "symbols": ["pw_init"]} | |
| 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} | |
| 7 | 7 | ], | |
| 8 | 8 | "symbols": [ | |
| 9 | - | {"name": "pw_init", "library": "nonexistent", "signature": "func()"} | |
| 9 | + | {"name": "pw_init", "library": "nonexistent", "group": "init", "signature": "func()"} | |
| 10 | 10 | ] | |
| 11 | 11 | } | |
| @@ -3,9 +3,9 @@ | |||||
| 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"]} | 6 | + | {"name": "init", "interface": "InitAPI", "package": "out", "symbols": ["pw_init"]} |
| 7 | ], | 7 | ], | ||
| 8 | "symbols": [ | 8 | "symbols": [ | ||
| 9 | - | {"name": "pw_init", "library": "nonexistent", "signature": "func()"} | 9 | + | {"name": "pw_init", "library": "nonexistent", "group": "init", "signature": "func()"} |
| 10 | ] | 10 | ] | ||
| 11 | } | 11 | } | ||
M go.mod +2 -0
| @@ -1,3 +1,5 @@ | |||
| 1 | 1 | module github.com/bnema/purego-pipewire | |
| 2 | 2 | ||
| 3 | 3 | go 1.23 | |
| 4 | + | ||
| 5 | + | require github.com/ebitengine/purego v0.10.0 // indirect | |
| @@ -1,3 +1,5 @@ | |||||
| 1 | module github.com/bnema/purego-pipewire | 1 | module github.com/bnema/purego-pipewire | ||
| 2 | 2 | ||||
| 3 | go 1.23 | 3 | go 1.23 | ||
| 4 | + | ||||
| 5 | + | require github.com/ebitengine/purego v0.10.0 // indirect | |||