1
0
Fork 0

Add MultiPackageAnalyzer with cross-package import resolution

This commit is contained in:
Brendan Szymanski 2026-07-01 20:10:17 -04:00
parent bc67617649
commit 9eab7ffc72

View 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
)
}
}