diff --git a/Sources/SwiftGtkGenCore/CodeGen.swift b/Sources/SwiftGtkGenCore/CodeGen.swift index 9d7ef56..1835ec3 100644 --- a/Sources/SwiftGtkGenCore/CodeGen.swift +++ b/Sources/SwiftGtkGenCore/CodeGen.swift @@ -196,6 +196,115 @@ public struct CodeGenerator { return files } + /// Generates Swift source for all modules in a monorepo. + /// + /// Iterates over all packages in the analysis, producing per-type Swift + /// files for each module. Each file includes the appropriate `import` + /// statements for the module's direct dependencies. + /// + /// - Parameter analysis: The resolved multi-package analysis. + /// - Returns: Module name → (filename → source code) nested dictionary. + /// - Throws: `CodeGenError` if generation fails. + public func generateMonorepo(analysis: MultiPackageAnalysis) throws -> [String: [String: String]] { + var allOutputs: [String: [String: String]] = [:] + + for (moduleName, repo) in analysis.repositories { + guard let cfg = analysis.packageConfigs[moduleName] else { continue } + guard let ns = repo.namespaces.first(where: { $0.name == moduleName }) + ?? repo.namespaces.first else { continue } + + let directImports = analysis.directDependencies[moduleName] ?? [] + let nsToModule = buildNamespaceToModuleMap(from: analysis) + _ = nsToModule + + let rootModuleName = moduleName + + let analyzer = Analyzer(config: cfg) + let singleAnalysis = analyzer.analyze(repository: repo) + + let classNames = Set(ns.classes.map(\.name)) + let shouldGenerateAll = singleAnalysis.generatedTypes.isEmpty + + var sourceFiles: [String: String] = [:] + + let header = fileHeader(moduleName: rootModuleName, directImports: directImports) + + for cls in ns.classes { + let fullName = "\(ns.name).\(cls.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(cls.name).swift"] = header + "\n" + generateClass(cls, namespace: ns.name, analysis: singleAnalysis, classTypeNames: classNames) + } + for enm in ns.enumerations { + let fullName = "\(ns.name).\(enm.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(enm.name).swift"] = header + "\n" + generateEnum(enm) + } + for bf in ns.bitfields { + let fullName = "\(ns.name).\(bf.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(bf.name).swift"] = header + "\n" + generateBitfield(bf) + } + for iface in ns.interfaces { + let fullName = "\(ns.name).\(iface.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(iface.name).swift"] = header + "\n" + generateInterface(iface) + } + for cb in ns.callbacks { + let fullName = "\(ns.name).\(cb.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(cb.name).swift"] = header + "\n" + generateCallback(cb) + } + for fn in ns.functions { + let fullName = "\(ns.name).\(fn.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(Self.pascalCaseName(fn.name)).swift"] = header + "\n" + generateGlobalFunction(fn, namespace: ns.name, classTypeNames: classNames) + } + for rec in ns.records { + let fullName = "\(ns.name).\(rec.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(rec.name).swift"] = header + "\n" + generateRecord(rec, namespace: ns.name, analysis: singleAnalysis, classTypeNames: classNames) + } + for cst in ns.constants { + let fullName = "\(ns.name).\(cst.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(cst.name).swift"] = header + "\n" + generateConstant(cst) + } + for alias in ns.aliases { + let fullName = "\(ns.name).\(alias.name)" + if !shouldGenerateAll && !singleAnalysis.generatedTypes.contains(fullName) { continue } + sourceFiles["\(alias.name).swift"] = header + "\n" + generateAlias(alias) + } + + allOutputs[rootModuleName] = sourceFiles + } + + return allOutputs + } + + /// Generates the standard file header for monorepo source files. + private func fileHeader(moduleName: String, directImports: Set) -> String { + var imports = "import C\(moduleName)\nimport Foundation\n" + for imp in directImports.sorted() { + imports += "import \(imp)\n" + } + return """ + // Generated by SwiftGtkGen. DO NOT EDIT. + + \(imports) + """ + } + + /// Builds a mapping from GIR namespace name to Swift module name. + private func buildNamespaceToModuleMap(from analysis: MultiPackageAnalysis) -> [String: String] { + var map: [String: String] = [:] + for (moduleName, repo) in analysis.repositories { + for ns in repo.namespaces { + map[ns.name] = moduleName + } + } + return map + } + // MARK: - Class Generation /// Generates a Swift class declaration from a GIR class definition. @@ -954,6 +1063,32 @@ public struct CodeGenerator { } } + /// Converts a GIR type to a Swift type string, qualifying cross-namespace + /// type references with their Swift module name prefix. + /// + /// Cross-namespace references (`.typeRef(name, namespace: "Gdk")`) produce + /// `Gdk.Rectangle` when the current module is not `Gdk`. Same-namespace + /// references produce the bare `name`. + /// + /// - Parameters: + /// - type: The GIR type to map. + /// - currentModule: The Swift module name currently being generated. + /// - nsToModule: Map from GIR namespace name to Swift module name. + /// - Returns: A qualified Swift type name. + static func qualifiedTypeToSwift(_ type: GIRType, currentModule: String, nsToModule: [String: String]) -> String { + switch type { + case .typeRef(let name, let namespace): + if let ns = namespace, let module = nsToModule[ns], module != currentModule { + return "\(module).\(name)" + } + return name + case .array(let inner): return "[\(qualifiedTypeToSwift(inner, currentModule: currentModule, nsToModule: nsToModule))]" + case .cArray(let inner): return "UnsafeBufferPointer<\(qualifiedTypeToSwift(inner, currentModule: currentModule, nsToModule: nsToModule))>" + case .optional(let inner): return "\(qualifiedTypeToSwift(inner, currentModule: currentModule, nsToModule: nsToModule))?" + default: return Self.typeToSwift(type) + } + } + /// Converts a snake_case GIR name to a camelCase Swift property name. /// /// Splits on underscores and lowercases the first part while capitalizing