Add MultiPackageAnalyzer with cross-package import resolution
This commit is contained in:
parent
bc67617649
commit
9eab7ffc72
1 changed files with 129 additions and 0 deletions
129
Sources/SwiftGtkGenCore/MultiPackageAnalysis.swift
Normal file
129
Sources/SwiftGtkGenCore/MultiPackageAnalysis.swift
Normal file
|
|
@ -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 `<include>` elements in each GIR).
|
||||
public let directDependencies: [String: Set<String>]
|
||||
|
||||
/// 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<String>]
|
||||
|
||||
/// 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<String>]
|
||||
|
||||
/// Per-package `GenerationConfig` for driving per-type code generation.
|
||||
public let packageConfigs: [String: GenerationConfig]
|
||||
|
||||
public init(repositories: [String: Repository],
|
||||
directDependencies: [String: Set<String>],
|
||||
transitiveDependencies: [String: Set<String>],
|
||||
implicitImports: [String: Set<String>],
|
||||
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<String>] = [:]
|
||||
|
||||
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<String>()
|
||||
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<String>] = [:]
|
||||
|
||||
func computeTransitive(for module: String, visited: inout Set<String>) -> Set<String> {
|
||||
if let cached = transitiveDeps[module] { return cached }
|
||||
visited.insert(module)
|
||||
var result = Set<String>()
|
||||
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<String>()
|
||||
_ = 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<String>] = [:]
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue