Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More changes for generating gstreamer bindings #159

Draft
wants to merge 7 commits into
base: 4
Choose a base branch
from
Draft
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
52 changes: 38 additions & 14 deletions gir/girgen/cmt/cmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ type ParamDoc struct {
// fields should rarely be used, because they're too magical. All fields inside
// InfoFields are optional.
type InfoFields struct {
Name *string
Attrs *gir.InfoAttrs
Elements *gir.InfoElements
ParamDocs []ParamDoc
ReturnDocs []ParamDoc
Name *string
CType *string
CIdentifier *string
Attrs *gir.InfoAttrs
Elements *gir.InfoElements
ParamDocs []ParamDoc
ReturnDocs []ParamDoc
}

func getField(value reflect.Value, field string) interface{} {
Expand Down Expand Up @@ -93,6 +95,15 @@ func GetInfoFields(v interface{}) InfoFields {
var inf InfoFields

inf.Name, _ = getField(value, "Name").(*string)
inf.CType, _ = getField(value, "CType").(*string) // for types
inf.CIdentifier, _ = getField(value, "CIdentifier").(*string) // for constants
if inf.CIdentifier == nil && inf.CType == nil {
attrs, _ := getField(value, "CallableAttrs").(*gir.CallableAttrs) // for methods

if attrs != nil {
inf.CIdentifier = &attrs.CIdentifier
}
}
inf.Attrs, _ = getField(value, "InfoAttrs").(*gir.InfoAttrs)
inf.Elements, _ = getField(value, "InfoElements").(*gir.InfoElements)
inf.ParamDocs, _ = getField(value, "ParamDocs").([]ParamDoc)
Expand Down Expand Up @@ -213,12 +224,21 @@ func goDoc(v interface{}, indentLvl int, opts []Option) string {

var self string
var orig string
var cIdentifier string

if inf.Name != nil {
orig = *inf.Name
self = strcases.Go(orig)
}

if inf.CType != nil {
cIdentifier = *inf.CType
}

if inf.CIdentifier != nil {
cIdentifier = *inf.CIdentifier
}

var docBuilder strings.Builder
if inf.Elements != nil && inf.Elements.Doc != nil {
docBuilder.WriteString(inf.Elements.Doc.String)
Expand Down Expand Up @@ -255,7 +275,7 @@ func goDoc(v interface{}, indentLvl int, opts []Option) string {

synopsize := searchOptsBool(opts, synopsize{})

docStr := FixGrammar(self, docBuilder.String(), append(opts, originalTypeName(orig))...)
docStr := fixGrammar(self, cIdentifier, docBuilder.String(), append(opts, originalTypeName(orig))...)
if synopsize && docStr == "" {
return ""
}
Expand Down Expand Up @@ -341,7 +361,7 @@ func writeParamDocs(tail *strings.Builder, label string, params []ParamDoc) {

var doc string
if param.InfoElements.Doc != nil {
doc = FixGrammar(name, param.InfoElements.Doc.String, originalTypeName(param.Name))
doc = fixGrammar(name, "", param.InfoElements.Doc.String, originalTypeName(param.Name))
// Insert a dash space into the lines.
doc = transformLines(doc, func(i, _ int, line string) string {
if i == 0 {
Expand Down Expand Up @@ -391,13 +411,17 @@ func trimFirstWord(paragraph string) string {
return parts[1]
}

// FixGrammar takes a comment and fixes its grammar by adding the [self] name
// fixGrammar takes a comment and fixes its grammar by adding the [self] name
// where appropriate to make it more idiomatic.
func FixGrammar(self, cmt string, opts ...Option) string {
func fixGrammar(self, cIdentifier, cmt string, opts ...Option) string {
if cmt == "" {
return ""
}

if cIdentifier != "" {
cIdentifier = " (" + cIdentifier + ")"
}

cmt = html.UnescapeString(cmt)

if self != "" {
Expand All @@ -422,20 +446,20 @@ func FixGrammar(self, cmt string, opts ...Option) string {
switch {
case strings.EqualFold(firstWord, "is"), strings.EqualFold(firstWord, "will"):
// Turn "Will frob the fnord" into "{name} will frob the fnord".
cmt = self + " " + lowerFirstLetter(cmt)
cmt = self + cIdentifier + " " + lowerFirstLetter(cmt)
case strings.EqualFold(firstWord, "emitted"):
// Turn "emitted by Emitter" into "{name} is emitted by Emitter".
cmt = self + " is " + lowerFirstLetter(cmt)
cmt = self + cIdentifier + " is " + lowerFirstLetter(cmt)
case typeNamed, strings.HasPrefix(cmt, "#") && nthWord(cmt, 1) != "":
// Turn "#CName does stuff" into "GoName does stuff"
cmt = self + " " + trimFirstWord(cmt)
cmt = self + cIdentifier + " " + trimFirstWord(cmt)
case wordIsSimplePresent(firstWord):
// Turn "Frobs the fnord" into "{name} frobs the fnord".
cmt = self + " " + lowerFirstLetter(cmt)
cmt = self + cIdentifier + " " + lowerFirstLetter(cmt)
default:
// Trim the word "this" away to make the sentence gramatically
// correct.
cmt = self + ": " + lowerFirstLetter(strings.TrimPrefix(cmt, "this "))
cmt = self + cIdentifier + ": " + lowerFirstLetter(strings.TrimPrefix(cmt, "this "))
}
}

Expand Down
12 changes: 12 additions & 0 deletions gir/girgen/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,12 @@ func (f *GoFileGenerator) Generate() ([]byte, error) {
fpen.Words("// #cgo CFLAGS: -Wno-deprecated-declarations")
}

if defines := f.CDefines(); len(defines) > 0 {
for _, macro := range defines {
fpen.Linef("// #define %s", macro)
}
}

fpen.Words("// #include <stdlib.h>")
if incls := f.CIncludes(); len(incls) > 0 {
for _, incl := range incls {
Expand Down Expand Up @@ -317,6 +323,12 @@ func (f *GoFileGenerator) CIncludes() []string {
return namespaceCIncludes(f.current, &f.header)
}

// CIncludes returns this file's sorted C includes, including the repository's C
// includes.
func (f *GoFileGenerator) CDefines() []string {
return f.header.SortedCDefines()
}

func namespaceCIncludes(n *gir.NamespaceFindResult, h *file.Header) []string {
extraIncludes := h.SortedCIncludes()

Expand Down
25 changes: 25 additions & 0 deletions gir/girgen/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type Header struct {
Marshalers []Marshaler
Imports map[string]string
CIncludes map[string]struct{}
Defines map[string]struct{}
Packages map[string]struct{} // for pkg-config
Callbacks map[string]struct{} // used for C blocks in general
CallbackDelete bool
Expand Down Expand Up @@ -273,6 +274,30 @@ func (h *Header) SortedCIncludes() []string {
return includes
}

// DefineC defines a macro using #define
func (h *Header) DefineC(macro string) {
if h.stop {
return
}

if h.Defines == nil {
h.Defines = map[string]struct{}{}
}

h.Defines[macro] = struct{}{}
}

// SortedCDefines returns the list of C defines sorted.
func (h *Header) SortedCDefines() []string {
defines := make([]string, 0, len(h.Defines))
for macro := range h.Defines {
defines = append(defines, macro)
}

sort.Strings(defines)
return defines
}

// NeedsGLibObject adds the glib-object.h include and the glib-2.0 package.
func (h *Header) NeedsGLibObject() {
// Need this for g_value_get_boxed.
Expand Down
24 changes: 6 additions & 18 deletions gir/girgen/generators/class-interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,31 +68,21 @@ var classInterfaceTmpl = gotmpl.NewGoTemplate(`

{{ $needsPrivate := false }}

{{ if .Abstract }}

{{ if .IsClass }}
// {{ .InterfaceName }} describes types inherited from class {{ .StructName }}.
{{ $needsPrivate = true -}}
// {{ .InterfaceName }} describes types inherited from {{ .StructName }}.
//
// To get the original type, the caller must assert this to an interface or
// another type.
type {{ .InterfaceName }} interface {
coreglib.Objector
base{{ .StructName }}() *{{ .StructName }}
}
{{ else }}
// {{ .InterfaceName }} describes {{ .StructName }}'s interface methods.
type {{ .InterfaceName }} interface {
coreglib.Objector
{{- range .ParentNames }}
{{ . }}
{{- end }}

{{ if .Methods -}}

{{ range .Methods }}
{{ if $.IsInSameFile . -}}
{{- Synopsis . 1 TrailingNewLine }}
{{ Synopsis . 1 TrailingNewLine }}
{{- .Name }}{{ .Tail }}
{{- end }}
{{- end }}

{{ range .Signals }}
{{ Synopsis . 1 TrailingNewLine }}
Expand All @@ -101,15 +91,13 @@ var classInterfaceTmpl = gotmpl.NewGoTemplate(`

{{- end}}

{{ if not .Methods -}}
{{ if or (not .Methods) .IsClass -}}
{{ $needsPrivate = true -}}
base{{ .StructName }}() *{{ .StructName }}
{{ end -}}
}
{{ end }}

var _ {{ .InterfaceName }} = (*{{ .StructName }})(nil)
{{ end }}

{{ if .GLibTypeStruct }}
{{ if .IsClass }}
Expand Down
21 changes: 21 additions & 0 deletions gir/girgen/generators/iface/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ func (g *Generator) Use(typ interface{}) bool {
}

for i, sig := range signals {
// signals with the G_SIGNAL_ACTION flag are there to be freely emitted by user code. They also can
// be thought of as methods on generic objects.
if sig.Action {
continue // TODO: generate a type safe call for emit instead, similar to how we do for connect
}

// A signal has 2 implied parameters: the instance (0th) parameter and
// the final user_data parameter.
param := &gir.Parameters{
Expand Down Expand Up @@ -480,6 +486,21 @@ func (g *Generator) ImplInterfaces() []string {
return names
}

func (g *Generator) ParentNames() []string {
parents := g.Tree.Requires
names := make([]string, len(parents))

for i, parent := range parents {
namespace := parent.NeedsNamespace(g.gen.Namespace())
if namespace {
parent.ImportPubl(g.gen, &g.header)
}
names[i] = parent.PublicType(namespace)
}

return names
}

// IsInSameFile returns true if the given GIR item is in the same file. It's
// guessed using the InfoElements field in the given value.
func (g *Generator) IsInSameFile(v interface{}) bool {
Expand Down
39 changes: 27 additions & 12 deletions gir/girgen/types/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,27 +143,42 @@ func PreserveGetName(girType string) Preprocessor {
})
}

// RenameEnumMembers renames all members of the matched enums. It is primarily
// RenameEnumMembers renames the c-identifier of members of the matched enum or bitfield. It is primarily
// used to avoid collisions.
func RenameEnumMembers(enum, regex, replace string) Preprocessor {
girTypeMustBeVersioned(enum)
re := regexp.MustCompile(regex)
return MapMembers(enum, func(member gir.Member) gir.Member {
parts := strings.SplitN(member.CIdentifier, "_", 2)
parts[1] = re.ReplaceAllString(parts[1], replace)
member.CIdentifier = parts[0] + "_" + parts[1]

return member
})
}

// MapMembers allows you to freely change any property of the members of the given enum or bitfield type.
// it can be used to fix faulty girs, but also to change the behavior of the generator.
func MapMembers(enumOrBitfieldType string, fn func(member gir.Member) gir.Member) Preprocessor {
girTypeMustBeVersioned(enumOrBitfieldType)
return PreprocessorFunc(func(repos gir.Repositories) {
result := repos.FindFullType(enum)
result := repos.FindFullType(enumOrBitfieldType)
if result == nil {
log.Printf("GIR enum %q not found", enum)
log.Panicf("GIR enum or bitfield %q not found", enumOrBitfieldType)
return
}

enum, ok := result.Type.(*gir.Enum)
if !ok {
log.Panicf("GIR type %T is not enum", result.Type)
}

for i, member := range enum.Members {
parts := strings.SplitN(member.CIdentifier, "_", 2)
parts[1] = re.ReplaceAllString(parts[1], replace)
enum.Members[i].CIdentifier = parts[0] + "_" + parts[1]
switch v := result.Type.(type) {
case *gir.Enum:
for i, member := range v.Members {
v.Members[i] = fn(member)
}
case *gir.Bitfield:
for i, member := range v.Members {
v.Members[i] = fn(member)
}
default:
log.Panicf("GIR type %T is not enum or bitfield", result.Type)
}
})
}
Expand Down
47 changes: 47 additions & 0 deletions gir/girgen/types/typeconv/c-go.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,53 @@ func (conv *Converter) cgoConverter(value *ValueConverted) bool {
}
return true

case "Gst.MiniObject", "Gst.Structure", "Gst.Caps", "Gst.Buffer", "Gst.BufferList", "Gst.Memory", "Gst.Message", "Gst.Query", "Gst.Sample":
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I'm likely not going to accept this PR as-is. This diff is a good study for me to add additional APIs into the GIR generator that would permit this for external generators, but adding edge cases from external generators into gotk4 directly is not sustainable in the long term.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this diff around so I can go back and reference it, but I would strongly recommend moving this to a draft PR so that it doesn't block this PR from being merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm completely on your side. I'd like to move the cases for the special types into the go-gst repo, and add some functionality to this PR that allows me to hook into this function.

I'm not quite sure how to do this though, because this is fairly deep into the call stack and quite far from the genmain.Data. If you have a suggestion please let me know.

value.header.Import("runtime")
value.header.Import("unsafe")
value.header.ImportCore("gextras")

// Require 1 pointer to avoid weird copies.
value.vtmpl(`
<.Out.Set> = <.OutCast 1>(gextras.NewStructNative(unsafe.Pointer(<.InNamePtr 1>)))
`)
if value.fail {
value.Logln(logger.Debug, "record set fail")
return false
}

if value.TransferOwnership.TransferOwnership == "none" {
value.vtmpl("C.gst_mini_object_ref((*C.GstMiniObject)(unsafe.Pointer(<.InNamePtr 1>)))")
}

if value.TransferOwnership.TransferOwnership != "borrow" {
value.vtmpl(`
runtime.SetFinalizer(
gextras.StructIntern(unsafe.Pointer(<.OutInPtr 1><.OutName>)),
func(intern *struct{ C unsafe.Pointer }) {
C.gst_mini_object_unref((*C.GstMiniObject)(intern.C))
})
`)
}

if value.TransferOwnership.TransferOwnership == "borrow" && len(conv.Results) == 0 {
panic("result should borrow even though callable has no param")
}

if value.TransferOwnership.TransferOwnership == "borrow" {
// the returned value must keep the instance alive, which is the first passed param, aka the receiver
value.vtmpl(fmt.Sprintf(`
runtime.SetFinalizer( // value is borrowed, don't clean up the receiver until dropped
gextras.StructIntern(unsafe.Pointer(<.OutInPtr 1><.OutName>)),
func(_ *struct{ C unsafe.Pointer }) {
runtime.KeepAlive(%s)
},
)`,
conv.Results[0].InName,
))
}

return true

case "GObject.Object", "GObject.InitiallyUnowned":
return value.cgoSetObject(conv)

Expand Down
Loading
Loading