1// package pass implements the first and second pass to go from gir files to go files
2// the first pass collects basic type information
3// the second pass uses the basic type information to go over the gir files again and convert it to go files
4package pass
5
6import (
7 "encoding/xml"
8 "fmt"
9 "os"
10 "strings"
11 "text/template"
12
13 "github.com/bnema/puregotk/internal/gir/types"
14 "github.com/bnema/puregotk/internal/gir/util"
15)
16
17type Pass struct {
18 Parsed []types.Repository
19 Types types.KindMap
20}
21
22// New creates a new pass struct by parsing gir files in the string slice
23// This pass object will then be used to go over these files multiple times up until we have the full info to convert it to go files
24func New(files []string) (*Pass, error) {
25 p := Pass{
26 Parsed: make([]types.Repository, len(files)),
27 Types: make(types.KindMap),
28 }
29 for i, f := range files {
30 b, err := os.ReadFile(f)
31 if err != nil {
32 return nil, err
33 }
34 var r types.Repository
35 err = xml.Unmarshal(b, &r)
36 if err != nil {
37 return nil, err
38 }
39 p.Parsed[i] = r
40 }
41 return &p, nil
42}
43
44func (p *Pass) collectTypes(r types.Repository) {
45 ns := r.Namespaces[0]
46 for _, cls := range ns.Classes {
47 p.Types.Add(ns.Name, cls.Name, types.ClassesType, cls)
48 }
49 for _, rec := range ns.Records {
50 p.Types.Add(ns.Name, rec.Name, types.RecordsType, rec)
51 }
52 for _, en := range ns.Enums {
53 // TODO: This probably shouldn't be aliastype, but we should make dedicated types
54 p.Types.Add(ns.Name, en.Name, types.AliasType, en)
55 }
56 for _, cb := range ns.Callbacks {
57 p.Types.Add(ns.Name, cb.Name, types.CallbackType, cb)
58 }
59 for _, b := range ns.Bitfields {
60 // TODO: This probably shouldn't be aliastype, but we should make dedicated types
61 p.Types.Add(ns.Name, b.Name, types.AliasType, b)
62 }
63 for _, inter := range ns.Interfaces {
64 p.Types.Add(ns.Name, inter.Name, types.InterfacesType, inter)
65 }
66 for _, alias := range ns.Aliases {
67 // Check what the alias points to and use the same type
68 aliasTarget := alias.Type.Name
69 if aliasTarget != "" {
70 targetKind := p.Types.Kind(ns.Name, aliasTarget)
71 if targetKind != types.UnknownType {
72 p.Types.Add(ns.Name, alias.Name, targetKind, alias)
73 } else {
74 // If we don't know the target type yet, default to alias type
75 p.Types.Add(ns.Name, alias.Name, types.AliasType, alias)
76 }
77 } else {
78 p.Types.Add(ns.Name, alias.Name, types.AliasType, alias)
79 }
80 }
81}
82
83// First does a "first pass" meaning it collects basic type information for all the repositories
84func (p *Pass) First() {
85 for _, r := range p.Parsed {
86 p.collectTypes(r)
87 }
88}
89
90func (p *Pass) writeGo(r types.Repository, gotemp *template.Template, dir string) {
91 ns := r.Namespaces[0]
92
93 aliases := make(map[string][]types.AliasTemplate)
94 enums := make(map[string][]types.EnumTemplate)
95 var files []string
96 for _, el := range ns.Bitfields {
97 temp := el.Template(ns.Name)
98 fn := el.FilenameSafe()
99 files = append(files, fn)
100 enums[fn] = append(enums[fn], temp)
101 }
102
103 for _, el := range ns.Enums {
104 temp := el.Template(ns.Name)
105 fn := el.FilenameSafe()
106 files = append(files, fn)
107 enums[fn] = append(enums[fn], temp)
108 }
109
110 constants := make(map[string][]types.ConstantTemplate)
111 for _, con := range ns.Constants {
112 fn := con.FilenameSafe()
113 files = append(files, fn)
114 constants[fn] = append(constants[fn], con.Template(ns.Name, p.Types))
115 }
116
117 callbackDocs := make(map[string]string)
118 for _, cb := range ns.Callbacks {
119 callbackDocs[cb.Name] = cb.Doc.StringSafe()
120 }
121
122 records := make(map[string][]types.RecordTemplate)
123 recordLookup := make(map[string]bool)
124 for _, rec := range ns.Records {
125 name := util.SnakeToCamel(rec.Name)
126 constructors := make([]types.FuncTemplate, len(rec.Constructors))
127 receivers := make([]types.FuncTemplate, 0, len(rec.Methods))
128 fields := make([]types.RecordField, 0, len(rec.Fields))
129 callbackAccessors := make([]types.CallbackAccessor, 0)
130 fn := rec.FilenameSafe()
131 files = append(files, fn)
132 for i, c := range rec.Constructors {
133 constructors[i] = types.FuncTemplate{
134 Name: util.ConstructorName(c.Name, rec.Name),
135 CName: c.CIdentifier,
136 Doc: c.Doc.StringSafe(),
137 Args: c.Parameters.Template(ns.Name, "", p.Types, c.Throws, types.ArgsFromGoToC),
138 Ret: c.ReturnValue.Template(ns.Name, "", p.Types, c.Throws),
139 }
140 }
141 for _, f := range rec.Fields {
142 var _type string
143 var fieldName string
144
145 // Check if this field is a callback
146 if f.Callback != nil {
147 _type = "uintptr"
148 fieldName = "x" + util.SnakeToCamel(f.Name) // Prefix callback pointer fields with `x` to make them private
149
150 callbackName := util.SnakeToCamel(f.Name)
151 args := f.Callback.Parameters.Template(ns.Name, "", p.Types, f.Callback.Throws, types.ArgsFromCToGo)
152 ret := f.Callback.ReturnValue.Template(ns.Name, "", p.Types, f.Callback.Throws)
153
154 apiTypes := args.API.Types
155
156 var doc string
157 if f.Callback.Doc != nil && f.Callback.Doc.String != "" {
158 doc = f.Callback.Doc.StringSafe()
159 } else {
160 baseClassName := strings.TrimSuffix(rec.Name, "Class")
161 callbackName := baseClassName + util.SnakeToCamel(f.Name) + "Func"
162
163 if callbackDoc, exists := callbackDocs[callbackName]; exists && callbackDoc != "" {
164 doc = callbackDoc
165 } else {
166 doc = f.Doc.StringSafe()
167 }
168 }
169
170 callbackAccessors = append(callbackAccessors, types.CallbackAccessor{
171 Name: callbackName,
172 CName: f.Name,
173 Doc: doc,
174 CallbackType: "func(" + strings.Join(apiTypes, ", ") + ") " + ret.Value,
175 Args: args,
176 Ret: ret,
177 })
178 } else {
179 _type = f.Translate(ns.Name, p.Types)
180 if _type == "" {
181 continue
182 }
183 // HACK: Handle the specific case where a gint is converted to an int
184 // But for structs this needs to be an int32 as purego just gets the pointer to the struct
185 // Instead of converting each field separately
186 if f.AnyType.Type != nil && f.AnyType.Type.CType == "gint" {
187 _type = "int32"
188 }
189
190 // HACK: in structs the strings should be uintptr as we convert it ourselves
191 if _type == "string" {
192 _type = "uintptr"
193 }
194
195 // HACK: Special handling for parent_class field - it should be embedded as a full struct
196 // to match C's memory layout, not converted to uintptr
197 // See https://docs.gtk.org/gobject/tutorial.html
198 if f.Name == "parent_class" && f.AnyType.Type != nil {
199 // Check if this is a Record type with no pointers (embedded struct)
200 typeName := util.NormalizeNamespace(ns.Name, f.AnyType.Type.Name, true)
201 kind := p.Types.Kind(ns.Name, typeName)
202 if kind == types.RecordsType && !strings.Contains(f.AnyType.Type.CType, "*") {
203 // Use the full struct type for embedding
204 _type = typeName
205 }
206 }
207
208 fieldName = util.SnakeToCamel(f.Name)
209 }
210
211 fields = append(fields, types.RecordField{
212 Name: fieldName,
213 Type: _type,
214 })
215 }
216 for _, f := range rec.Methods {
217 name := util.SnakeToCamel(f.Name)
218 if name == "" {
219 name = util.SnakeToCamel(f.CIdentifier)
220 }
221 for _, f := range fields {
222 if f.Name == name {
223 name = name + "Fn"
224 break
225 }
226 }
227 receivers = append(receivers, types.FuncTemplate{
228 Doc: f.Doc.StringSafe(),
229 Name: name,
230 CName: f.CIdentifier,
231 Args: f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
232 Ret: f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
233 })
234 }
235 records[fn] = append(records[fn], types.RecordTemplate{
236 Name: name,
237 Doc: rec.Doc.StringSafe(),
238 Constructors: constructors,
239 Receivers: receivers,
240 Fields: fields,
241 CallbackAccessors: callbackAccessors,
242 TypeGetter: rec.GLibGetType,
243 })
244 recordLookup[name] = true
245 }
246
247 callbacks := make(map[string][]types.CallbackTemplate)
248 // set every callback equal to uintptr as well
249 for _, cb := range ns.Callbacks {
250 fn := cb.FilenameSafe()
251 files = append(files, fn)
252 cbT := types.CallbackTemplate{
253 Doc: cb.Doc.StringSafe(),
254 Name: cb.Name,
255 Args: cb.Parameters.Template(ns.Name, "", p.Types, cb.Throws, types.ArgsFromCToGo),
256 Ret: cb.ReturnValue.Template(ns.Name, "", p.Types, cb.Throws),
257 }
258 callbacks[fn] = append(callbacks[fn], cbT)
259 }
260
261 interfaces := make(map[string][]types.InterfaceTemplate)
262 for _, inter := range ns.Interfaces {
263 fn := inter.FilenameSafe()
264 files = append(files, fn)
265 interfaces[fn] = append(interfaces[fn], types.ConvertInterface(ns.Name, "", inter, nil, p.Types))
266 }
267
268 for _, union := range ns.Unions {
269 fn := union.FilenameSafe()
270 files = append(files, fn)
271 name := util.SnakeToCamel(union.Name)
272 interT := types.AliasTemplate{
273 Doc: union.Doc.StringSafe(),
274 Name: name,
275 // structs are not yet supported in CGO
276 Value: "uintptr",
277 }
278 aliases[fn] = append(aliases[fn], interT)
279 }
280
281 for _, alias := range ns.Aliases {
282 fn := alias.FilenameSafe()
283 files = append(files, fn)
284 typeName := alias.Template(ns.Name, p.Types)
285 if typeName == "" {
286 typeName = "uintptr"
287 }
288 name := util.SnakeToCamel(alias.Name)
289 aliasT := types.AliasTemplate{
290 Doc: alias.Doc.StringSafe(),
291 Name: name,
292 // structs are not yet supported in CGO
293 Value: typeName,
294 }
295 aliases[fn] = append(aliases[fn], aliasT)
296 }
297
298 functions := make(map[string][]types.FuncTemplate)
299 for _, f := range ns.Functions {
300 name := util.SnakeToCamel(f.Name)
301 if p.Types.Kind(ns.Name, name) != types.UnknownType {
302 name = "New" + name
303 }
304 fn := f.FilenameSafe()
305 files = append(files, fn)
306 functions[fn] = append(functions[fn], types.FuncTemplate{
307 Name: name,
308 CName: f.CIdentifier,
309 Doc: f.Doc.StringSafe(),
310 Args: f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
311 Ret: f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
312 })
313 }
314
315 classes := make(map[string][]types.ClassTemplate)
316 for _, cls := range ns.Classes {
317 implemented := make(map[string]bool)
318 constructors := make([]types.FuncTemplate, len(cls.Constructors))
319 functions := make([]types.FuncTemplate, len(cls.Functions))
320 fn := cls.FilenameSafe()
321 files = append(files, fn)
322
323 for i, c := range cls.Constructors {
324 c.ReturnValue.AnyType.Type.Name = cls.Name
325 constructors[i] = types.FuncTemplate{
326 Name: util.ConstructorName(c.Name, cls.Name),
327 CName: c.CIdentifier,
328 Doc: c.Doc.StringSafe(),
329 Args: c.Parameters.Template(ns.Name, "", p.Types, c.Throws, types.ArgsFromGoToC),
330 Ret: c.ReturnValue.Template(ns.Name, "", p.Types, c.Throws),
331 }
332 }
333 signals := make([]types.SignalsTemplate, len(cls.Signals))
334 for i, s := range cls.Signals {
335 signals[i] = types.SignalsTemplate{
336 Doc: s.Doc.StringSafe(),
337 Name: util.DashToCamel(s.Name),
338 CName: s.Name,
339 Args: s.Parameters.Template(ns.Name, "", p.Types, false, types.ArgsFromCToGo),
340 Ret: s.ReturnValue.Template(ns.Name, "", p.Types, false),
341 Detailed: s.Detailed,
342 }
343 }
344 receivers := make([]types.FuncTemplate, len(cls.Methods))
345 for i, f := range cls.Methods {
346 name := util.SnakeToCamel(f.Name)
347 implemented[name] = true
348 receivers[i] = types.FuncTemplate{
349 Doc: f.Doc.StringSafe(),
350 Name: name,
351 CName: f.CIdentifier,
352 Args: f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
353 Ret: f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
354 }
355 }
356 var interfaces []types.InterfaceTemplate
357 for i, f := range cls.Functions {
358 name := fmt.Sprintf("%s%s", util.SnakeToCamel(cls.Name), util.SnakeToCamel(f.Name))
359 functions[i] = types.FuncTemplate{
360 Name: name,
361 CName: f.CIdentifier,
362 Doc: f.Doc.StringSafe(),
363 Args: f.Parameters.Template(ns.Name, "", p.Types, f.Throws, types.ArgsFromGoToC),
364 Ret: f.ReturnValue.Template(ns.Name, "", p.Types, f.Throws),
365 }
366 }
367 for _, impl := range cls.Implements {
368 interfaces = append(interfaces, types.GetInterfaceFuncs(ns.Name, impl.Name, implemented, p.Types))
369 }
370 properties := make([]types.PropertyTemplate, 0, len(cls.Properties))
371 for _, prop := range cls.Properties {
372 propTemp := prop.Template(ns.Name, p.Types)
373
374 // TODO: Implement non-primitive types, then remove this
375 if propTemp.GValueType != "" {
376 properties = append(properties, propTemp)
377 }
378 }
379 classes[fn] = append(classes[fn], types.ClassTemplate{
380 Doc: cls.Doc.StringSafe(),
381 Name: cls.Name,
382 Parent: util.NormalizeNamespace(ns.Name, cls.Parent, true),
383 Constructors: constructors,
384 Receivers: receivers,
385 Interfaces: interfaces,
386 Functions: functions,
387 Properties: properties,
388 Signals: signals,
389 TypeGetter: cls.GLibGetType,
390 })
391 }
392
393 pkgName := strings.ToLower(ns.Name)
394
395 var pkgConfigName string
396 if len(r.Packages) > 0 {
397 pkgConfigName = r.Packages[0].Name
398 }
399
400 var sharedLibraries []string
401 if ns.SharedLibrary != "" {
402 for _, lib := range strings.Split(ns.SharedLibrary, ",") {
403 if trimmed := strings.TrimSpace(lib); trimmed != "" {
404 sharedLibraries = append(sharedLibraries, trimmed)
405 }
406 }
407 }
408
409 for _, fn := range files {
410 methods := 0
411 for _, i := range interfaces[fn] {
412 methods += len(i.Methods)
413 }
414 for _, i := range records[fn] {
415 methods += len(i.Constructors)
416 methods += len(i.Receivers)
417 }
418 for _, i := range classes[fn] {
419 methods += len(i.Constructors)
420 methods += len(i.Receivers)
421 methods += len(i.Functions)
422 }
423 // we do not need to add the length of interfaces in here
424 // as they should only be loaded when there are classes
425 needsInit := (len(functions[fn]) + methods) > 0
426
427 // Check if any receiver method has callback parameters
428 // This is used to conditionally import unsafe and purego
429 hasReceiverCallbacks := false
430 for _, rec := range records[fn] {
431 for _, r := range rec.Receivers {
432 if len(r.Args.Callbacks) > 0 {
433 hasReceiverCallbacks = true
434 break
435 }
436 }
437 if hasReceiverCallbacks {
438 break
439 }
440 }
441 if !hasReceiverCallbacks {
442 for _, cls := range classes[fn] {
443 for _, r := range cls.Receivers {
444 if len(r.Args.Callbacks) > 0 {
445 hasReceiverCallbacks = true
446 break
447 }
448 }
449 if hasReceiverCallbacks {
450 break
451 }
452 }
453 }
454
455 // Check if any standalone function has callback parameters
456 hasFunctionCallbacks := false
457 for _, f := range functions[fn] {
458 if len(f.Args.Callbacks) > 0 {
459 hasFunctionCallbacks = true
460 break
461 }
462 }
463
464 needsCoreHelpers := false
465 checkFuncArgs := func(funcs []types.FuncTemplate) {
466 if needsCoreHelpers {
467 return
468 }
469 for _, f := range funcs {
470 if f.Args.NeedsCore() {
471 needsCoreHelpers = true
472 return
473 }
474 }
475 }
476 checkInterfaceArgs := func(funcs []types.InterfaceFuncTemplate) {
477 if needsCoreHelpers {
478 return
479 }
480 for _, f := range funcs {
481 if f.Args.NeedsCore() {
482 needsCoreHelpers = true
483 return
484 }
485 }
486 }
487
488 for _, rec := range records[fn] {
489 checkFuncArgs(rec.Constructors)
490 checkFuncArgs(rec.Receivers)
491 }
492 for _, cls := range classes[fn] {
493 checkFuncArgs(cls.Constructors)
494 checkFuncArgs(cls.Receivers)
495 checkFuncArgs(cls.Functions)
496 }
497 checkFuncArgs(functions[fn])
498 for _, inter := range interfaces[fn] {
499 checkInterfaceArgs(inter.Methods)
500 }
501
502 // Check if any signal has string parameters that need core.GoString()
503 hasSignalStrings := false
504 for _, cls := range classes[fn] {
505 for _, sig := range cls.Signals {
506 if sig.Args.HasPureStrings() {
507 hasSignalStrings = true
508 break
509 }
510 }
511 if hasSignalStrings {
512 break
513 }
514 }
515
516 // Scan all types for cross-package references (glib.*, gobject.*, types.*).
517 needsGLib, needsGObject, hasTypeGetters, crossPkgImports := scanCrossPackageRefs(
518 pkgName, functions[fn], records[fn], classes[fn], interfaces[fn])
519 // TypeGetters are emitted by the template when .TypeGetter != "", but
520 // the string "types.GType" only appears in template output — not in the
521 // Go data structures that scanCrossPackageRefs inspects. Check explicitly.
522 if !hasTypeGetters {
523 for _, r := range records[fn] {
524 if r.TypeGetter != "" {
525 hasTypeGetters = true
526 break
527 }
528 }
529 }
530 if !hasTypeGetters {
531 for _, c := range classes[fn] {
532 if c.TypeGetter != "" {
533 hasTypeGetters = true
534 break
535 }
536 }
537 }
538 if !hasTypeGetters {
539 for _, i := range interfaces[fn] {
540 if i.TypeGetter != "" {
541 hasTypeGetters = true
542 break
543 }
544 }
545 }
546 if !hasTypeGetters {
547 for _, a := range aliases[fn] {
548 if a.TypeGetter != "" {
549 hasTypeGetters = true
550 break
551 }
552 }
553 }
554 if !hasTypeGetters {
555 for _, e := range enums[fn] {
556 if e.TypeGetter != "" {
557 hasTypeGetters = true
558 break
559 }
560 }
561 }
562
563 args := types.TemplateArg{
564 PkgName: pkgName,
565 PkgEnv: strings.ToUpper(pkgName),
566 PkgConfigName: pkgConfigName,
567 SharedLibraries: sharedLibraries,
568 NeedsInit: needsInit,
569 NeedsCore: needsCoreHelpers,
570 HasReceiverCallbacks: hasReceiverCallbacks,
571 HasFunctionCallbacks: hasFunctionCallbacks,
572 HasSignalStrings: hasSignalStrings,
573 HasTypeGetters: hasTypeGetters,
574 NeedsGLib: needsGLib,
575 NeedsGObject: needsGObject,
576 CrossPkgImports: crossPkgImports,
577 Aliases: aliases[fn],
578 Callbacks: callbacks[fn],
579 Records: records[fn],
580 Enums: enums[fn],
581 Constants: constants[fn],
582 Functions: functions[fn],
583 Interfaces: interfaces[fn],
584 Classes: classes[fn],
585 }
586
587 os.MkdirAll(fmt.Sprintf(dir+"/%s", pkgName), 0o755)
588
589 f, err := os.Create(fmt.Sprintf(dir+"/%s/%s", pkgName, fn))
590 if err != nil {
591 panic(err)
592 }
593 err = gotemp.Execute(f, args)
594 if err != nil {
595 panic(err)
596 }
597
598 }
599}
600
601func (p *Pass) Second(dir string, gotemp *template.Template) {
602 for _, r := range p.Parsed {
603 p.writeGo(r, gotemp, dir)
604 }
605}
606
607// scanCrossPackageRefs checks if any generated code in the file references
608// glib.* or gobject.* symbols in pure types, return types, record fields,
609// class parents, signal args, or callback accessor types.
610func scanCrossPackageRefs(
611 pkgName string,
612 funcs []types.FuncTemplate,
613 recs []types.RecordTemplate,
614 classes []types.ClassTemplate,
615 ifaces []types.InterfaceTemplate,
616) (needsGLib, needsGObject, needsTypes bool, crossPkgImports []string) {
617 // Track all cross-package references (e.g. "gio.", "cairo.", "pango.")
618 crossPkgs := make(map[string]bool)
619 check := func(s string) {
620 if !needsGLib && strings.Contains(s, "glib.") {
621 needsGLib = true
622 }
623 if !needsGObject && strings.Contains(s, "gobject.") {
624 needsGObject = true
625 }
626 if !needsTypes && strings.Contains(s, "types.") {
627 needsTypes = true
628 }
629 // Detect any "pkg." references for cross-package imports
630 for _, pkg := range []string{"gio", "cairo", "pango", "pangocairo", "graphene", "gsk", "gdk"} {
631 if strings.Contains(s, pkg+".") {
632 crossPkgs[pkg] = true
633 }
634 }
635 }
636 checkFuncTypes := func(f types.FuncTemplate) {
637 check(f.Ret.Raw)
638 check(f.Ret.Value)
639 // RefSink return types trigger gobject.IncreaseRef in template Fmt()
640 if f.Ret.RefSink {
641 needsGObject = true
642 }
643 // Throws trigger glib.Error in template Preamble()
644 if f.Ret.Throws {
645 needsGLib = true
646 }
647 for _, t := range f.Args.Pure.Types {
648 check(t)
649 }
650 for _, t := range f.Args.API.Types {
651 check(t)
652 }
653 }
654
655 for _, f := range funcs {
656 checkFuncTypes(f)
657 // Callback parameters generate glib.GetCallback/SaveCallbackWithClosure
658 // and gobject.SignalConnect calls in the template output.
659 if len(f.Args.Callbacks) > 0 {
660 needsGLib = true
661 }
662 }
663 for _, r := range recs {
664 for _, f := range r.Fields {
665 check(f.Type)
666 }
667 for _, c := range r.Constructors {
668 checkFuncTypes(c)
669 }
670 for _, m := range r.Receivers {
671 checkFuncTypes(m)
672 if len(m.Args.Callbacks) > 0 {
673 needsGLib = true
674 }
675 }
676 for _, ca := range r.CallbackAccessors {
677 check(ca.CallbackType)
678 }
679 }
680 for _, c := range classes {
681 check(c.Parent)
682 // Signals use glib helpers + gobject.SignalConnect
683 if len(c.Signals) > 0 {
684 needsGLib = true
685 needsGObject = true
686 }
687 for _, con := range c.Constructors {
688 checkFuncTypes(con)
689 }
690 for _, m := range c.Receivers {
691 checkFuncTypes(m)
692 }
693 for _, f := range c.Functions {
694 checkFuncTypes(f)
695 }
696 for _, s := range c.Signals {
697 for _, t := range s.Args.Pure.Types {
698 check(t)
699 }
700 for _, t := range s.Args.API.Types {
701 check(t)
702 }
703 }
704 // Class properties use gobject.Value
705 if len(c.Properties) > 0 {
706 needsGObject = true
707 }
708 // Class interface implementations (e.g. gio.ListModel methods on pango.FontFamily)
709 for _, iface := range c.Interfaces {
710 for _, m := range iface.Methods {
711 checkFuncTypes(m.FuncTemplate)
712 if m.Namespace != "" {
713 check(m.Namespace)
714 }
715 }
716 if len(iface.Properties) > 0 {
717 needsGObject = true
718 }
719 }
720 }
721 for _, i := range ifaces {
722 for _, m := range i.Methods {
723 checkFuncTypes(m.FuncTemplate)
724 // Interface methods have a Namespace prefix (e.g. "gio.")
725 if m.Namespace != "" {
726 check(m.Namespace)
727 }
728 }
729 // Interface properties use gobject.Value/Object for get/set
730 if len(i.Properties) > 0 {
731 needsGObject = true
732 needsGLib = true // glib.StrvGetType etc. used in property vector types
733 }
734 for _, p := range i.Properties {
735 check(p.GoType)
736 }
737 // Interface methods with callbacks need glib for GetCallback/SaveCallbackWithClosure
738 for _, m := range i.Methods {
739 if len(m.FuncTemplate.Args.Callbacks) > 0 {
740 needsGLib = true
741 }
742 }
743 }
744 // A package never needs to import itself, and glib/gobject cannot
745 // import each other (circular dependency).
746 if strings.EqualFold(pkgName, "glib") {
747 needsGLib = false
748 needsGObject = false // glib cannot import gobject (cycle)
749 }
750 if strings.EqualFold(pkgName, "gobject") {
751 needsGObject = false
752 // gobject CAN import glib (no cycle: glib does not import gobject)
753 }
754 delete(crossPkgs, strings.ToLower(pkgName))
755 // glib, gobject, and types are handled by dedicated template conditions
756 delete(crossPkgs, "glib")
757 delete(crossPkgs, "gobject")
758 // Build import paths for detected cross-package references
759 const moduleBase = "github.com/bnema/puregotk/v4/"
760 for pkg := range crossPkgs {
761 crossPkgImports = append(crossPkgImports, moduleBase+pkg)
762 }
763 return
764}