Root causes fixed, all in TypeMapper.swift / TypeRegistry.swift / Planner.swift / PlanRenderer.swift / BindingPlan.swift / IRModel.swift / XMLParser.swift: - Global (cross-module) inherited-member post-pass in planModules replaces the old in-module-only version, dropping properties/methods redeclared by an ancestor in a DIFFERENT module (e.g. Pango.Coverage.ref()/unref() vs. GObject.Object) - transfer-ownership="none" GObject-class returns now route through init(retaining:) instead of adopting a borrowed pointer as owned - Non-GObject fundamental root classes (GParamSpec) capture their GIR glib:ref-func/glib:unref-func instead of hardcoding g_object_ref - Interface conformance clauses annotate each interface with @MainActor to satisfy isolated-conformance checking across module boundaries - Scalar marshalOut (.direct/.numericCast) returned through a pointer C type with no <array> length now skips instead of misreading a raw buffer pointer as its pointee value - Property/method Swift-name collisions on the same class are resolved by dropping the property (e.g. Pango.FontFamily.isVariable) - MultiPackageAnalyzer only walks includes for configured packages - No unconditional Foundation import (collided with Gio.InputStream) - gdk_pixbuf_non_anim_new added to knownMissingCFunctions (GIR-declared, not exported through the public umbrella header) Smoke tests: smoke/tier3/PixbufSmoke.swift (Pixbuf construction + dimension round-trip against real libgdk_pixbuf) and smoke/tier3/PangoSmoke.swift (findBaseDir against real libpango, Latin vs. Hebrew script direction). 208 unit tests pass (+14: InheritedMemberTests + E2 sections in PropertyGenerationTests/RendererCallableTests/others). compile-gate.sh 1, 2, and 3 all PASS. smoke-test.sh 3 passes 21/21 (18 tier-1 base + 1 GdkPixbuf + 2 Pango). |
||
|---|---|---|
| configs | ||
| docs/skip-baseline | ||
| scripts | ||
| smoke | ||
| Sources | ||
| Tests | ||
| .gitignore | ||
| .swift-format | ||
| Package.swift | ||
| README.md | ||
gobject-generator — GIR-to-Swift Binding Generator
Reads GObject Introspection (.gir) XML files and produces low-level Swift wrapper libraries for GTK and its dependency stack. Each wrapper package is a self-contained SwiftPM project with a Clang module map for C interop and per-type Swift source files.
Usage
swift-gtk-gen --gir-file /usr/share/gir-1.0/Gtk-4.0.gir --output ./Gtk
Library name and version are derived automatically from the GIR file's <namespace> element. Override via config.toml (see below).
Arguments
| Argument | Description | Example |
|---|---|---|
--gir-file PATH |
Path to the .gir XML file to process (required). |
/usr/share/gir-1.0/Gtk-4.0.gir |
--output DIR |
Output directory for generated files (default: .). |
./Sources/GTK |
--config-toml PATH |
Path to config.toml for per-type config (default: config.toml). |
Sources/GTK/config.toml |
--namespace NAME |
Namespace to generate (for multi-namespace GIR files). | Gtk |
--list-output-files |
Print expected output file paths and exit. | — |
--girs-dirs DIRS |
Comma-separated list of directories to search for .gir files. |
/usr/share/gir-1.0,vendor/gir-files |
--external-libs LIBS |
Comma-separated external library names for link flags. | Gdk,Gsk,Pango |
--generate TYPES |
Comma-separated list of types to generate (e.g. Gtk.Widget,Gtk.Window). When combined with --generate-all, this is ignored. |
Gtk.Widget,Gtk.Align |
--generate-all |
Generate all types found in the namespace. Default if --generate is omitted. |
— |
--manual TYPES |
Comma-separated types to mark as manually implemented (skipped during generation). | Gtk.Buildable |
--ignore TYPES |
Comma-separated types to skip entirely. | Gtk.Test |
--emit-single-file |
Emit a single concatenated Swift file instead of one file per type. | — |
--help, -h |
Show help text. | — |
config.toml
Per-type configuration can be specified via a TOML file. Library name and version are auto-extracted from the GIR namespace. Example:
target_directory = "Sources/GTK"
girs_directories = ["/usr/share/gir-1.0"]
external_libraries = ["Gdk-4.0", "Gsk-4.0"]
generate = ["Gtk.Widget", "Gtk.Window", "Gtk.Button"]
manual = ["Gtk.Buildable"]
ignore = ["Gtk.Test"]
[[objects]]
name = "Gtk.Widget"
concurrency = "mainActor"
visibility = "public"
[[objects]]
name = "Gtk.Align"
rename = "Alignment"
Generate all types
swift-gtk-gen --gir-file Gtk-4.0.gir --output ./Gtk --generate-all
Generate specific types
swift-gtk-gen --gir-file Gtk-4.0.gir --output ./Gtk \
--generate Gtk.Widget,Gtk.Window,Gtk.Button,Gtk.Align
Output Structure
./Gtk/
├── Package.swift
├── Sources/
│ ├── CGtk/
│ │ ├── CGtk.h
│ │ └── module.modulemap
│ └── Gtk/
│ ├── Widget.swift
│ ├── Window.swift
│ ├── Button.swift
│ ├── Align.swift
│ └── ...
Package.swift— SwiftPM manifest with asystemLibrarytarget (for C interop via pkgConfig) and a Swift target for the generated wrappers.Sources/C<Library>/— Clang module map and umbrella header, derived from the GIR file's<c:include>andshared-libraryattributes.Sources/<Library>/— One.swiftfile per generated type (class, protocol, enum, OptionSet, callback typealias, free function).
Generated Code Example
The following is a sample of generated output for a subset of GTK types:
/// The base class for all widgets.
///
/// It manages the widget lifecycle, layout, states and style.
public final class Widget: GObject.InitiallyUnowned {
let pointer: UnsafeMutableRawPointer
public init(pointer: UnsafeMutableRawPointer) {
g_object_ref_sink(pointer)
self.pointer = pointer
}
deinit {
g_object_unref(pointer)
}
public var halign: Align {
get {
var value = GValue()
g_value_init(&value, G_TYPE_OBJECT)
g_object_get_property(pointer, "halign", &value)
let result = g_value_get_object(&value)
g_value_unset(&value)
return result
}
set {
var value = GValue()
g_value_init(&value, G_TYPE_OBJECT)
g_value_set_object(&value, newValue)
g_object_set_property(pointer, "halign", &value)
g_value_unset(&value)
}
}
public func show() {
gtk_widget_show(pointer)
}
public func getVisible() -> Bool {
return (gtk_widget_get_visible(pointer) != 0)
}
public func getParent() -> Widget? {
return gtk_widget_get_parent(pointer).map { Widget(pointer: $0) }
}
public func connectDestroy(_ handler: @escaping () -> Void) -> Int {
let boxed = Unmanaged.passRetained(handler as AnyObject).toOpaque()
let callback: @convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void =
{ (_, data) in
let stored = Unmanaged<AnyObject>.fromOpaque(data!).takeUnretainedValue() as! () -> Void
stored()
}
let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = {
Unmanaged<AnyObject>.fromOpaque($0!).release()
}
return Int(g_signal_connect_data(pointer, "destroy", callback, boxed, destroy, 0))
}
}
/// stretch to fill all space, but align the baseline.
public enum Align: Int {
case fill = 0
case start = 1
case end = 2
case center = 3
case baselineFill = 4
case baseline = 4
case baselineCenter = 5
}
What Gets Generated
| GIR Type | Swift Output |
|---|---|
<class> |
final class with UnsafeMutableRawPointer storage, ref-counted init/deinit, GValue-based properties, C function-call methods, g_signal_connect_data signal handlers, convenience constructors |
<interface> |
protocol with method requirements and property requirements |
<enumeration> |
enum: Int with camelCase cases |
<bitfield> |
struct: OptionSet with rawValue: Int and bit-shifted constants |
<callback> |
typealias with @convention(c) |
<function> |
public func wrapping the C function call with type conversion |
Generated Package Buildability
The generated Package.swift includes a systemLibrary target with pkgConfig for the native library. The host system must have the corresponding development packages installed (e.g. libgtk-4-dev for Gtk, libglib2.0-dev for GLib) for the generated package to compile.
Development
# Build the generator
swift build
# Run the test suite
swift test
# Format generated code (uses .swift-format config)
swift format lint --configuration .swift-format path/to/generated/file.swift