diff --git a/Sources/SwiftGtkGenCore/MultiPackageAnalysis.swift b/Sources/SwiftGtkGenCore/MultiPackageAnalysis.swift new file mode 100644 index 0000000..c86aa00 --- /dev/null +++ b/Sources/SwiftGtkGenCore/MultiPackageAnalysis.swift @@ -0,0 +1,129 @@ +import Foundation + +/// Result of analyzing multiple GIR repositories together. +/// Resolves the full cross-package dependency graph and determines +/// which `import` statements each generated file needs. +public struct MultiPackageAnalysis { + /// Maps Swift module name → parsed Repository. + public let repositories: [String: Repository] + + /// Maps Swift module name → names of directly depended-upon modules + /// (derived from `` elements in each GIR). + public let directDependencies: [String: Set] + + /// Maps Swift module name → full transitive closure of module imports + /// that should be re-exported by that module's umbrella. + /// A module's transitive deps are: all direct deps + all transitive + /// deps of each direct dep. Used to generate `@_exported import` lines. + public let transitiveDependencies: [String: Set] + + /// For a given module, the set of other Swift modules whose types + /// are referenced in that module's generated code but are NOT direct + /// dependencies (i.e., modules accessed transitively through a direct + /// dep that re-exports them). Always empty in the re-export approach + /// because transitive types come through re-exports. + public let implicitImports: [String: Set] + + /// Per-package `GenerationConfig` for driving per-type code generation. + public let packageConfigs: [String: GenerationConfig] + + public init(repositories: [String: Repository], + directDependencies: [String: Set], + transitiveDependencies: [String: Set], + implicitImports: [String: Set], + packageConfigs: [String: GenerationConfig]) { + self.repositories = repositories + self.directDependencies = directDependencies + self.transitiveDependencies = transitiveDependencies + self.implicitImports = implicitImports + self.packageConfigs = packageConfigs + } +} + +/// Analyzes multiple GIR `Repository` instances to produce a +/// `MultiPackageAnalysis` with resolved cross-package imports. +public struct MultiPackageAnalyzer { + public let config: MonorepoConfig + + public init(config: MonorepoConfig) { + self.config = config + } + + /// Parses all GIR files and resolves the dependency graph. + public func analyze() throws -> MultiPackageAnalysis { + var repositories: [String: Repository] = [:] + var directDeps: [String: Set] = [:] + + let parser = GIRParser() + + for entry in config.packages { + let repo = try parser.parse(fileURL: URL(fileURLWithPath: entry.girPath)) + repositories[entry.name] = repo + + var deps = Set() + for include in repo.includedPackages { + deps.insert(include.swiftModule) + } + directDeps[entry.name] = deps + } + + // Compute transitive closure for re-exports. + // A module's transitive deps = union of its direct deps plus each + // direct dep's transitive deps, transitively. + var transitiveDeps: [String: Set] = [:] + + func computeTransitive(for module: String, visited: inout Set) -> Set { + if let cached = transitiveDeps[module] { return cached } + visited.insert(module) + var result = Set() + if let dirs = directDeps[module] { + for dep in dirs { + if visited.contains(dep) { continue } + result.insert(dep) + result.formUnion(computeTransitive(for: dep, visited: &visited)) + } + } + visited.remove(module) + transitiveDeps[module] = result + return result + } + + for moduleName in config.packages.map(\.name) { + var visited = Set() + _ = computeTransitive(for: moduleName, visited: &visited) + } + + // implicitImports: in a re-export lattice there are no implicit + // imports — every needed module is reached via transitive re-exports. + var implicitImports: [String: Set] = [:] + for moduleName in config.packages.map(\.name) { + implicitImports[moduleName] = [] + } + + // Build per-package GenerationConfigs + var packageConfigs: [String: GenerationConfig] = [:] + for entry in config.packages { + let extLibs = directDeps[entry.name]?.sorted() ?? [] + let cfg = GenerationConfig( + library: entry.name, + version: repositories[entry.name]?.namespaces.first?.version ?? "", + girsDirectories: [], + targetDirectory: "Sources/\(entry.name)", + externalLibraries: extLibs, + generate: [], + manual: [], + ignore: [], + objects: entry.objects + ) + packageConfigs[entry.name] = cfg + } + + return MultiPackageAnalysis( + repositories: repositories, + directDependencies: directDeps, + transitiveDependencies: transitiveDeps, + implicitImports: implicitImports, + packageConfigs: packageConfigs + ) + } +}