/// Top-level configuration for generating Swift bindings from a GIR repository. /// /// `GenerationConfig` is the Swift-native equivalent of a config file or TOML /// approach used by other GIR-based generators. It specifies which `.gir` files /// to read, where to emit output, which types to generate/manually implement/ /// ignore, and any per-type or per-function overrides. /// /// ### Example /// ```swift /// GenerationConfig( /// library: "Gtk", /// version: "4.0", /// girsDirectories: ["/usr/share/gir-1.0"], /// targetDirectory: "Sources/Gtk", /// externalLibraries: ["GLib", "GObject"], /// generate: ["Gtk.Widget", "Gtk.Window"], /// manual: ["Gtk.CustomWidget"], /// ignore: ["Gtk.DeprecatedType"], /// objects: [] /// ) /// ``` public struct GenerationConfig { /// The name of the library being wrapped (e.g. `"Gtk"`, `"GLib"`). public var library: String /// The version string of the GIR namespace (e.g. `"4.0"`). public var version: String /// Directories to search for `.gir` files. public var girsDirectories: [String] /// Directory where generated Swift source files should be written. public var targetDirectory: String /// Names of external libraries whose types may be referenced (e.g. `"GLib"`, `"GObject"`). public var externalLibraries: [String] /// Fully-qualified type names for which Swift bindings should be generated. public var generate: [String] /// Fully-qualified type names that are implemented manually and should not be generated. public var manual: [String] /// Fully-qualified type names that should be skipped entirely. public var ignore: [String] /// Per-type and per-function override entries. public var objects: [ObjectConfig] /// Creates a complete generation configuration. /// /// - Parameters: /// - library: The library name (e.g. `"Gtk"`). /// - version: The GIR namespace version (e.g. `"4.0"`). /// - girsDirectories: Paths to search for `.gir` files. /// - targetDirectory: Output directory for generated Swift sources. /// - externalLibraries: Referenced external library names. /// - generate: Types to generate bindings for. /// - manual: Types handled by hand-written code. /// - ignore: Types to skip. /// - objects: Override entries for types, functions, signals, and properties. public init(library: String, version: String, girsDirectories: [String], targetDirectory: String, externalLibraries: [String], generate: [String], manual: [String], ignore: [String], objects: [ObjectConfig]) { self.library = library self.version = version self.girsDirectories = girsDirectories self.targetDirectory = targetDirectory self.externalLibraries = externalLibraries self.generate = generate self.manual = manual self.ignore = ignore self.objects = objects } } /// A configuration entry targeting a specific type, function, function pattern, /// signal, or property in the GIR repository. /// /// Each case carries the overrides or rename rules that should be applied /// during code generation for the matched element. public enum ObjectConfig { /// Overrides for a specific GObject type identified by its fully-qualified name. case object(_ name: String, overrides: ObjectOverrides) /// Overrides for a specific function on a type. case function(_ type: String, _ name: String, overrides: FunctionOverrides) /// A regex-based rename rule applied to matching functions on a type. case functionPattern(_ type: String, pattern: String, rename: RenameRule) /// Overrides for a specific signal on a type. case signal(_ type: String, _ name: String, overrides: SignalOverrides) /// Overrides for a specific property on a type. case property(_ type: String, _ name: String, overrides: PropertyOverrides) } /// Overrides that control how a single GObject type is treated during code generation. /// /// All properties are optional; only the values that are explicitly set will /// override the default generation behavior for the matched type. public struct ObjectOverrides { /// Whether to generate, mark as manual, or ignore this type. public var status: ObjectStatus? /// If `true`, mark the generated class as `final`. public var finalType: Bool? /// The concurrency model to apply (e.g. `@MainActor`, `Sendable`). public var concurrency: ConcurrencyModel? /// Minimum version string; the type is only generated when targeting this version or later. public var version: String? /// An optional `#if` compilation condition to guard the generated code. public var cfgCondition: String? /// If `true`, generate a builder pattern struct for constructing this type. public var generateBuilder: Bool? } /// Controls how a generated type should be treated. public enum ObjectStatus: String { /// Generate Swift bindings for this type. case generate /// This type will be implemented manually; do not generate. case manual /// Skip this type entirely. case ignore } /// The concurrency model to apply to a generated type. public enum ConcurrencyModel: String { /// Annotate the generated type with `@MainActor`. case mainActor /// Mark the generated type as `Sendable`. case sendable /// No special concurrency annotation. case none } /// Overrides that control how a single function is treated during code generation. /// /// All properties are optional; only the values that are explicitly set will /// override the default generation behavior for the matched function. public struct FunctionOverrides { /// If `true`, skip generating this function entirely. public var ignore: Bool? /// A rename rule to apply to this function's Swift name. public var rename: RenameRule? /// Minimum version string; only generate when targeting this version or later. public var version: String? /// An optional `#if` compilation condition to guard the generated code. public var cfgCondition: String? /// If `true`, treat this function as a constructor (returns a new instance). public var constructor: Bool? /// Override the visibility of the generated method. public var visibility: Visibility? /// Per-parameter overrides keyed by the parameter's original name. public var parameters: [String: ParameterOverride]? } /// The visibility level for a generated symbol. public enum Visibility: String { /// Visible outside the module. case `public` /// Visible only within the same module. case `internal` /// Visible to all modules in the same package. case `package` } /// Overrides for a single function parameter in the generated API. public struct ParameterOverride { /// If set, overrides whether the parameter is treated as nullable. public var nullable: Bool? /// If set, renames this parameter in the generated Swift function signature. public var newName: String? } /// Overrides that control how a GObject signal handler is generated. /// /// All properties are optional; only the explicitly set values override /// the default behavior for the matched signal. public struct SignalOverrides { /// If `true`, skip generating a handler API for this signal. public var ignore: Bool? /// If `true`, the signal can be inhibited (stopped from propagating). public var inhibit: Bool? /// Per-parameter overrides for the signal handler's closure parameters. public var parameters: [String: ParameterOverride]? } /// Overrides that control how a GObject property is exposed in the generated API. public struct PropertyOverrides { /// The accessor methods to generate for this property. /// /// If `nil`, the default set of accessors is generated based on the GIR /// metadata. Provide an explicit array to override which accessors are emitted. public var generate: [PropertyAccessor]? } /// The kind of accessor to generate for a GObject property. public enum PropertyAccessor: String { /// Generate a getter method for the property. case get /// Generate a setter method for the property. case set /// Generate a notification callback/handler for property changes. case notify } /// A regex-based rename rule for transforming GIR symbol names into Swift names. /// /// Matches the `regex` pattern against the original name and substitutes the /// `replacement` string, following standard regex capture-group semantics /// (e.g., `"$1"`, `"$2"`). /// /// ### Example /// ```swift /// RenameRule(regex: "^gtk_", replacement: "") /// ``` public struct RenameRule { /// The regular expression pattern to match against the original name. public var regex: String /// The replacement string, which may reference capture groups with `$1`, `$2`, etc. public var replacement: String /// Creates a rename rule with the given regex pattern and replacement. /// /// - Parameters: /// - regex: A regular expression pattern. /// - replacement: A replacement string (may include capture-group references). public init(regex: String, replacement: String) { self.regex = regex self.replacement = replacement } } extension GenerationConfig { /// Constructs a `GenerationConfig` from a TOML-parsed dictionary and the /// GIR namespace metadata. /// /// Library name and version are taken from `namespace` automatically; the /// TOML may optionally specify `target_directory` and /// `girs_directories`. All other fields (generate, manual, ignore, object /// overrides) are read from the TOML dictionary. /// /// - Parameters: /// - toml: The dictionary produced by `TOMLReader.parse(_:)`. /// - namespace: The GIR namespace providing `name` and `version`. /// - defaultTarget: Fallback target directory if TOML omits `target_directory`. /// - Returns: A fully-populated `GenerationConfig`. /// - Throws: `TOMLReaderError` if required fields are missing or malformed. public static func from(toml: [String: Any], namespace: Namespace, defaultTarget: String) throws -> GenerationConfig { let lib = namespace.name let ver = namespace.version let girsDirs = toml["girs_directories"] as? [String] ?? [] let targetDir = toml["target_directory"] as? String ?? defaultTarget let extLibs = toml["external_libraries"] as? [String] ?? [] let gen = toml["generate"] as? [String] ?? [] let man = toml["manual"] as? [String] ?? [] let ign = toml["ignore"] as? [String] ?? [] var objects: [ObjectConfig] = [] if let objectDict = toml["object"] as? [String: [String: Any]] { for name in objectDict.keys.sorted() { let overrides = try parseObjectOverrides(from: objectDict[name]!) objects.append(.object(name, overrides: overrides)) } } if let funcDict = toml["function"] as? [String: [String: [String: Any]]] { for typeName in funcDict.keys.sorted() { let methods = funcDict[typeName]! for funcName in methods.keys.sorted() { let fv = try parseFunctionOverrides(from: methods[funcName]!) objects.append(.function(typeName, funcName, overrides: fv)) } } } if let patternDict = toml["function_pattern"] as? [String: [String: [String: Any]]] { for typeName in patternDict.keys.sorted() { let entries = patternDict[typeName]! for pattern in entries.keys.sorted() { let pvDict = entries[pattern]! guard let renameDict = pvDict["rename"] as? [String: String], let regex = renameDict["regex"], let replacement = renameDict["replacement"] else { throw TOMLReaderError.parseError(line: 0, message: "function_pattern.\(typeName).\(pattern) missing rename rule") } let rename = RenameRule(regex: regex, replacement: replacement) objects.append(.functionPattern(typeName, pattern: pattern, rename: rename)) } } } if let sigDict = toml["signal"] as? [String: [String: [String: Any]]] { for typeName in sigDict.keys.sorted() { let signals = sigDict[typeName]! for sigName in signals.keys.sorted() { let ov = parseSignalOverrides(from: signals[sigName]!) objects.append(.signal(typeName, sigName, overrides: ov)) } } } if let propDict = toml["property"] as? [String: [String: [String: Any]]] { for typeName in propDict.keys.sorted() { let props = propDict[typeName]! for propName in props.keys.sorted() { let ov = parsePropertyOverrides(from: props[propName]!) objects.append(.property(typeName, propName, overrides: ov)) } } } return GenerationConfig( library: lib, version: ver, girsDirectories: girsDirs, targetDirectory: targetDir, externalLibraries: extLibs, generate: gen, manual: man, ignore: ign, objects: objects ) } } private func parseObjectOverrides(from dict: [String: Any]) throws -> ObjectOverrides { var ov = ObjectOverrides() if let st = dict["status"] as? String, let status = ObjectStatus(rawValue: st) { ov.status = status } if let ft = dict["final_type"] as? Bool { ov.finalType = ft } if let cm = dict["concurrency"] as? String, let cmv = ConcurrencyModel(rawValue: cm) { ov.concurrency = cmv } if let v = dict["version"] as? String { ov.version = v } if let cfg = dict["cfg_condition"] as? String { ov.cfgCondition = cfg } if let gb = dict["generate_builder"] as? Bool { ov.generateBuilder = gb } return ov } private func parseFunctionOverrides(from dict: [String: Any]) throws -> FunctionOverrides { var ov = FunctionOverrides() if let i = dict["ignore"] as? Bool { ov.ignore = i } if let renameDict = dict["rename"] as? [String: String] { ov.rename = RenameRule(regex: renameDict["regex"] ?? "", replacement: renameDict["replacement"] ?? "") } if let v = dict["version"] as? String { ov.version = v } if let cfg = dict["cfg_condition"] as? String { ov.cfgCondition = cfg } if let ct = dict["constructor"] as? Bool { ov.constructor = ct } if let vis = dict["visibility"] as? String, let vv = Visibility(rawValue: vis) { ov.visibility = vv } if let params = dict["parameter"] as? [String: [String: Any]] { ov.parameters = params.mapValues { pv in var po = ParameterOverride() if let n = pv["nullable"] as? Bool { po.nullable = n } if let n = pv["new_name"] as? String { po.newName = n } return po } } return ov } private func parseSignalOverrides(from dict: [String: Any]) -> SignalOverrides { var ov = SignalOverrides() if let i = dict["ignore"] as? Bool { ov.ignore = i } if let inh = dict["inhibit"] as? Bool { ov.inhibit = inh } if let params = dict["parameter"] as? [String: [String: Any]] { ov.parameters = params.mapValues { pv in var po = ParameterOverride() if let n = pv["nullable"] as? Bool { po.nullable = n } if let n = pv["new_name"] as? String { po.newName = n } return po } } return ov } private func parsePropertyOverrides(from dict: [String: Any]) -> PropertyOverrides { var ov = PropertyOverrides() if let genValues = dict["generate"] as? [String] { ov.generate = genValues.compactMap { PropertyAccessor(rawValue: $0) } } return ov } /// Configuration for generating a monorepo of Swift wrapper packages /// from multiple GIR files in a single generator invocation. /// /// `MonorepoConfig` allows a single generator run to produce multiple SwiftPM /// packages, each wrapping one GObject library. Packages are processed in order, /// with the first package treated as the leaf dependency and subsequent packages /// as dependents. /// /// ### Example /// ```toml /// [[packages]] /// name = "GLib" /// gir = "/usr/share/gir-1.0/GLib-2.0.gir" /// /// [[packages]] /// name = "GObject" /// gir = "/usr/share/gir-1.0/GObject-2.0.gir" /// concurrency = "sendable" /// ``` public struct MonorepoConfig { /// Root output directory for the monorepo (e.g. `"."` for the `gtk-swift/` workspace root). public let outputDir: String /// Entries for each package to generate, in dependency order (leaf dependency first). public let packages: [PackageEntry] /// Creates a monorepo generation configuration. /// /// - Parameters: /// - outputDir: The root directory for generated packages. /// - packages: Ordered list of package entries to generate. public init(outputDir: String, packages: [PackageEntry]) { self.outputDir = outputDir self.packages = packages } } /// A single GObject library wrapper package definition within the monorepo. /// /// Each `PackageEntry` specifies a `.gir` file to process and the Swift module /// to produce, along with any per-package concurrency or type overrides. public struct PackageEntry { /// Swift module name (e.g. `"Gtk"`, `"GObject"`, `"GLib"`). public let name: String /// Path to the `.gir` file for this library (e.g. `"/usr/share/gir-1.0/Gtk-4.0.gir"`). public let girPath: String /// Optional concurrency model override for this package's generated types. /// When `nil`, the default concurrency model derived from GIR metadata is used. public let concurrency: ConcurrencyModel? /// Per-type overrides for objects within this package (mirrors `GenerationConfig.objects`). public let objects: [ObjectConfig] /// Creates a package entry for the monorepo. /// /// - Parameters: /// - name: The Swift module name. /// - girPath: Path to the `.gir` file to process. /// - concurrency: An optional concurrency model override. Defaults to `nil`. /// - objects: Per-type override entries. Defaults to an empty array. public init(name: String, girPath: String, concurrency: ConcurrencyModel? = nil, objects: [ObjectConfig] = []) { self.name = name self.girPath = girPath self.concurrency = concurrency self.objects = objects } } /// A GIR `` dependency from one package to another. public struct IncludeEntry { /// The library name referenced by the include (e.g. "Gdk"). public let name: String /// The GIR version (e.g. "4.0"). public let version: String /// Derives the Swift module name (capitalized name portion). /// "Gdk" -> "Gdk", "GObject" -> "GObject". public var swiftModule: String { name } } extension MonorepoConfig { /// Parses a monorepo TOML config file using `[packages.Name]` section headers /// (the TOML reader supports `[section]` headers but not `[[array-of-tables]]`). /// /// Example config: /// ```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" /// concurrency = "mainActor" /// ``` public static func from(toml: [String: Any], defaultOutputDir: String) throws -> MonorepoConfig { let outputDir = toml["output_dir"] as? String ?? defaultOutputDir guard let packagesDict = toml["packages"] as? [String: [String: Any]] else { throw TOMLReaderError.parseError(line: 0, message: "monorepo config missing [packages.*] sections") } var packages: [PackageEntry] = [] for (name, pkgDict) in packagesDict.sorted(by: { $0.key < $1.key }) { guard let girPath = pkgDict["gir"] as? String else { throw TOMLReaderError.parseError(line: 0, message: "packages.\(name) missing 'gir'") } let concurrency = (pkgDict["concurrency"] as? String).flatMap(ConcurrencyModel.init(rawValue:)) let objects = try parseMonorepoObjectConfigs(from: pkgDict) packages.append(PackageEntry(name: name, girPath: girPath, concurrency: concurrency, objects: objects)) } return MonorepoConfig(outputDir: outputDir, packages: packages) } } private func parseMonorepoObjectConfigs(from pkgDict: [String: Any]) throws -> [ObjectConfig] { var objects: [ObjectConfig] = [] if let objDict = pkgDict["object"] as? [String: [String: Any]] { for name in objDict.keys.sorted() { let overrides = try parseObjectOverrides(from: objDict[name]!) objects.append(.object(name, overrides: overrides)) } } return objects }