From 8bfeea2ed28a8e15cf12c82a3cac73f756a49e02 Mon Sep 17 00:00:00 2001 From: Brendan Szymanski Date: Tue, 21 Jul 2026 20:30:01 -0400 Subject: [PATCH] Rewrite README and add MIT license --- LICENSE | 21 ++++ README.md | 330 ++++++++++++++++++++++++++++++++---------------------- 2 files changed, 218 insertions(+), 133 deletions(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d4856b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 gobject-generator contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 107585c..d0a6d58 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,218 @@ -# gobject-generator — GIR-to-Swift Binding Generator +# 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. +Reads GObject Introspection (`.gir`) XML files and produces a Swift monorepo of +low-level wrapper packages for GTK and its dependency stack (GLib, GObject, GIO, +Pango, GDK, GSK, GTK, Adwaita, Soup, GStreamer, and more). + +A single invocation generates every configured package - Swift sources, C bridge +targets, re-export umbrellas, and the root `Package.swift` - as a buildable +SwiftPM workspace. ## Usage ```bash -generator --gir-file /usr/share/gir-1.0/Gtk-4.0.gir --output ./Gtk +generator --monorepo-config config.toml --output ./Generated ``` -Library name and version are derived automatically from the GIR file's `` element. Override via `config.toml` (see below). +### Command-line arguments -### Arguments +| Argument | Description | +|---|---| +| `--monorepo-config PATH` | **Required.** Path to the monorepo TOML configuration file. | +| `--output DIR` | Root output directory for the generated monorepo (default: `.`). | +| `--skip-report` | Write per-module skip reports (`skip-reports/.json`) and a `coverage-summary.json` to the output directory. | +| `--smoke-target` | Add a `SmokeTests` test target to the generated `Package.swift`, depending on every generated module. | +| `--format-config PATH` | Path to a `.swift-format` configuration file used to format generated output. | +| `--help`, `-h` | Print usage information and exit. | -| 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. | — | +### Monorepo configuration file -### config.toml - -Per-type configuration can be specified via a TOML file. Library name and version are auto-extracted from the GIR namespace. Example: +The `--monorepo-config` argument points to a TOML file describing every package +to generate. Each `[packages.]` section declares a `.gir` file to process +and optional per-package overrides. ```toml -target_directory = "Sources/GTK" -girs_directories = ["/usr/share/gir-1.0"] -external_libraries = ["Gdk-4.0", "Gsk-4.0"] +# Top-level output directory (can be overridden by --output on the command line). +output_dir = "." -generate = ["Gtk.Widget", "Gtk.Window", "Gtk.Button"] -manual = ["Gtk.Buildable"] -ignore = ["Gtk.Test"] +# Each [packages.] section becomes one Swift module. +# Packages are listed in dependency order (leaf dependencies first). -[[objects]] -name = "Gtk.Widget" +[packages.GLib] +gir = "/usr/share/gir-1.0/GLib-2.0.gir" + +[packages.GObject] +gir = "/usr/share/gir-1.0/GObject-2.0.gir" concurrency = "mainActor" -visibility = "public" -[[objects]] -name = "Gtk.Align" -rename = "Alignment" +[packages.Gtk] +gir = "/usr/share/gir-1.0/Gtk-4.0.gir" + +# Per-type overrides are placed under [packages..object.]. +[packages.Gtk.object.Gtk.Widget] +status = "generate" # "generate" | "manual" | "ignore" +finalType = true +concurrency = "mainActor" # "mainActor" | "sendable" | "none" +version = "4.0" +cfgCondition = "os(macOS)" # #if compilation condition + +[packages.Gtk.object.Gtk.Builder] +generateBuilder = true ``` -### Generate all types +| Per-package key | Type | Description | +|---|---|---| +| `gir` | string | **Required.** Path to the `.gir` file to process. | +| `concurrency` | string | Default concurrency model for all types in this package: `"mainActor"`, `"sendable"`, or `"none"`. | -```bash -generator --gir-file Gtk-4.0.gir --output ./Gtk --generate-all -``` +| Per-type override (`object.`) | Type | Description | +|---|---|---| +| `status` | string | Override generation status: `"generate"`, `"manual"`, or `"ignore"`. | +| `finalType` | bool | Mark the generated class as `final`. | +| `concurrency` | string | Per-type concurrency model (same values as the package-level key). | +| `version` | string | Minimum version; type is only generated when targeting this version or later. | +| `cfgCondition` | string | An `#if` compilation condition wrapping the generated code. | +| `generateBuilder` | bool | Emit a builder-pattern struct for constructing this type. | -### Generate specific types - -```bash -generator --gir-file Gtk-4.0.gir --output ./Gtk \ - --generate Gtk.Widget,Gtk.Window,Gtk.Button,Gtk.Align - -## Output Structure +## Output structure ``` -./Gtk/ +/ ├── Package.swift ├── Sources/ +│ ├── CGLib/ +│ │ ├── CGLib.h # C umbrella header +│ │ └── module.modulemap # Clang module map (pkg-config driven) +│ ├── GLib/ +│ │ ├── GLib.swift # Re-export umbrella (@_exported import) +│ │ └── Generated/ +│ │ ├── Array.swift +│ │ ├── Error.swift +│ │ └── ... +│ ├── CGObject/ +│ │ └── ... +│ ├── GObject/ +│ │ ├── GObject.swift +│ │ └── Generated/ +│ │ └── ... +│ ├── CGtk/ +│ │ └── ... +│ └── Gtk/ +│ ├── Gtk.swift +│ └── Generated/ +│ └── ... +``` + +- **`Package.swift`** - SwiftPM manifest with a `.systemLibrary` target per C + bridge and a `.target` per Swift wrapper module. Dependencies between modules + are resolved automatically from the GIR `` graph. +- **`Sources/C/`** - One Clang module map and umbrella header per + generated Swift module, wired to the native library via `pkgConfig`. +- **`Sources//Generated/`** - One `.swift` file per generated type + (class, protocol, enum, option set, callback typealias, free function). +- **`Sources//.swift`** - A re-export umbrella importing every + transitive dependency, so consumers only need a single `import Gtk`. + +## User-defined test targets + +The generated `Package.swift` includes marker comments that preserve custom +targets across regenerations: + +```swift +// === USER TARGETS - preserved across generations === +// BEGIN_USER_TARGETS + +// END_USER_TARGETS +``` + +Add any custom `target()` entries (test targets, executables, plugins) between +these markers. The generator splices the existing content back into the freshly +generated `Package.swift` on every run. The markers must appear on their own +lines and must not be nested. + +Example: + +```swift +// BEGIN_USER_TARGETS + .testTarget(name: "MyIntegrationTests", dependencies: ["GLib", "Gtk"]), +// END_USER_TARGETS +``` + +## Example + +### Input: `config.toml` + +```toml +output_dir = "." + +[packages.GLib] +gir = "/usr/share/gir-1.0/GLib-2.0.gir" + +[packages.GObject] +gir = "/usr/share/gir-1.0/GObject-2.0.gir" + +[packages.Gtk] +gir = "/usr/share/gir-1.0/Gtk-4.0.gir" +``` + +### Command + +```bash +generator --monorepo-config config.toml --output ./gen +``` + +### Generated output + +``` +./gen/ +├── Package.swift +├── Sources/ +│ ├── CGLib/ +│ │ ├── CGLib.h +│ │ └── module.modulemap +│ ├── GLib/ +│ │ ├── GLib.swift +│ │ └── Generated/ +│ │ ├── Array.swift +│ │ ├── Error.swift +│ │ ├── ... +│ │ └── Variant.swift +│ ├── CGObject/ +│ │ ├── CGObject.h +│ │ └── module.modulemap +│ ├── GObject/ +│ │ ├── GObject.swift +│ │ └── Generated/ +│ │ ├── Binding.swift +│ │ ├── Object.swift +│ │ └── ... │ ├── CGtk/ │ │ ├── CGtk.h │ │ └── module.modulemap │ └── Gtk/ -│ ├── Widget.swift -│ ├── Window.swift -│ ├── Button.swift -│ ├── Align.swift -│ └── ... +│ ├── Gtk.swift +│ └── Generated/ +│ ├── Application.swift +│ ├── Widget.swift +│ ├── Window.swift +│ └── ... ``` -- **`Package.swift`** — SwiftPM manifest with a `systemLibrary` target (for C interop via pkgConfig) and a Swift target for the generated wrappers. -- **`Sources/C/`** — Clang module map and umbrella header, derived from the GIR file's `` and `shared-library` attributes. -- **`Sources//`** — One `.swift` file 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: +### Generated code sample ```swift +// Gtk/Generated/Widget.swift + /// 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) + init(takingOwnership pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + init(retaining pointer: UnsafeMutableRawPointer) { + g_object_ref(pointer) self.pointer = pointer } @@ -104,88 +220,36 @@ public final class Widget: GObject.InitiallyUnowned { 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.fromOpaque(data!).takeUnretainedValue() as! () -> Void - stored() - } - let destroy: @convention(c) (UnsafeMutableRawPointer?) -> Void = { - Unmanaged.fromOpaque($0!).release() - } - return Int(g_signal_connect_data(pointer, "destroy", callback, boxed, destroy, 0)) + public var visible: Bool { + get { gtk_widget_get_visible(pointer) != 0 } + set { gtk_widget_set_visible(pointer, newValue ? 1 : 0) } } } ``` -```swift -/// 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 | -|---|---| -| `` | `final class` with `UnsafeMutableRawPointer` storage, ref-counted `init`/`deinit`, GValue-based properties, C function-call methods, `g_signal_connect_data` signal handlers, convenience constructors | -| `` | `protocol` with method requirements and property requirements | -| `` | `enum: Int` with camelCase cases | -| `` | `struct: OptionSet` with `rawValue: Int` and bit-shifted constants | -| `` | `typealias` with `@convention(c)` | -| `` | `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 +## Building for development ```bash -# Build the generator +# Clone and build the generator +git clone +cd gobject-generator swift build -# Run the test suite +# Run the test suite (unit + integration) swift test -# Format generated code (uses .swift-format config) -swift format lint --configuration .swift-format path/to/generated/file.swift +# Generate bindings from a config +swift run generator --monorepo-config config.toml --output ./out + +# Format generated output (requires swift-format) +swift run generator --monorepo-config config.toml --format-config .swift-format ``` + +## License + +This project is licensed under the MIT License. See [LICENSE](LICENSE) for the +full text.