1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
/*
This source file is part of the Swift.org open source project
Copyright (c) 2021-2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Foundation
/// An accumulator for incrementally merging ``SymbolGraph``s into ``UnifiedSymbolGraph``s.
public class GraphCollector {
/// An indicator of whether a loaded symbol graph was a "primary" symbol graph or one that contained
/// extensions from a different module.
public enum GraphKind {
case primary(URL)
case `extension`(URL)
}
/// The list of merged symbol graphs, indexed by module name.
var unifiedGraphs: [String: UnifiedSymbolGraph] = [:]
/// The list of files that contributed to each merged symbol graph, indexed by module name.
var graphSources: [String: [GraphKind]] = [:]
var extensionGraphs: [URL: SymbolGraph] = [:]
private let extensionGraphAssociationStrategy: ExtensionGraphAssociation
/// Initialize a new collector for merging ``SymbolGraph``s into ``UnifiedSymbolGraph``s.
///
/// - Parameter extensionGraphAssociationStrategy: Optionally specify how extension graphs are to be merged.
public init(extensionGraphAssociationStrategy: ExtensionGraphAssociation = .extendedGraph) {
self.unifiedGraphs = [:]
self.graphSources = [:]
self.extensionGraphs = [:]
self.extensionGraphAssociationStrategy = extensionGraphAssociationStrategy
}
}
extension GraphCollector {
/// Describes which graph an extension graph (named `ExtendingModule@ExtendedModule.symbols.json`)
/// is merged with.
public enum ExtensionGraphAssociation {
/// Merge with the extending module
case extendingGraph
/// Merge with the extended module
case extendedGraph
}
}
extension GraphCollector {
/// Merges the given ``SymbolGraph`` into the set of unified symbol graphs.
///
/// - Parameters:
/// - inputGraph: The symbol graph to merge in.
/// - url: The file name where the given symbol graph is located. Used to determine whether a symbol graph
/// contains primary symbols or extensions.
public func mergeSymbolGraph(_ inputGraph: SymbolGraph, at url: URL, forceLoading: Bool = false) {
let (extendedModuleName, isMainSymbolGraph) = Self.moduleNameFor(inputGraph, at: url)
let moduleName = extensionGraphAssociationStrategy == .extendedGraph ? extendedModuleName : inputGraph.module.name
if !isMainSymbolGraph && !forceLoading {
self.extensionGraphs[url] = inputGraph
return
}
if let existingGraph = self.unifiedGraphs[moduleName] {
existingGraph.mergeGraph(graph: inputGraph, at: url)
} else {
self.unifiedGraphs[moduleName] = UnifiedSymbolGraph(fromSingleGraph: inputGraph, at: url)
}
let graphURL: GraphKind
if isMainSymbolGraph {
graphURL = .primary(url)
} else {
graphURL = .`extension`(url)
}
if var existingSources = self.graphSources[moduleName] {
existingSources.append(graphURL)
} else {
self.graphSources[moduleName] = [graphURL]
}
}
/// Finalizes the collected symbol graphs, loading in extension symbol graphs and processing orphan relationships.
///
/// - Parameter createOverloadGroups: Whether to create overload group symbols in the resulting unified symbol graphs.
/// If overload groups were created in the individual symbol graphs, they will be automatically combined regardless of this setting.
/// - Returns: A tuple containing a map of module names to unified symbol graphs, and a map of module names to symbol graph locations.
public func finishLoading(
createOverloadGroups: Bool = false
) -> (unifiedGraphs: [String: UnifiedSymbolGraph], graphSources: [String: [GraphKind]]) {
for (url, graph) in self.extensionGraphs {
self.mergeSymbolGraph(graph, at: url, forceLoading: true)
}
for (_, graph) in self.unifiedGraphs {
graph.collectOrphans()
if createOverloadGroups {
graph.createOverloadGroupSymbols()
} else {
graph.combineOverloadGroups()
}
}
return (self.unifiedGraphs, self.graphSources)
}
/// Determines the module name for the given symbol graph.
///
/// For Swift, symbol graphs are separated based on whether symbols are extending other symbols in other modules.
/// Symbols declared in the module itself are saved in a symbol graph file named `ModuleName.symbols.json`,
/// whereas symbols that extend a different module are saved in a separate file named
/// `ModuleName@OtherModule.symbols.json`. The latter symbol graph file still declares its module
/// as `ModuleName`, so this function allows consumers of symbol graphs to determine which module to load
/// symbols under.
///
/// - Parameters:
/// - graph: The symbol graph being checked.
/// - url: The file name where the symbol graph is located.
/// - Returns: The name of the module described by `graph`, and whether the symbol graph is a "primary" symbol graph.
public static func moduleNameFor(_ graph: SymbolGraph, at url: URL) -> (String, Bool) {
let isMainSymbolGraph = !url.lastPathComponent.contains("@")
let moduleName: String
if isMainSymbolGraph {
// For main symbol graphs, get the module name from the symbol graph's data
moduleName = graph.module.name
} else {
// For extension symbol graphs, derive the extended module's name from the file name.
//
// The per-symbol `extendedModule` value is the same as the main module for most symbols, so it's not a good way to find the name
// of the module that was extended (rdar://63200368).
let fileName = url.lastPathComponent.components(separatedBy: ".symbols.json")[0]
let fileNameComponents = fileName.components(separatedBy: "@")
if fileNameComponents.count > 2 {
// For a while, cross-import overlay symbol graphs had more than two components:
// "Framework1@Framework2@_Framework1_Framework2.symbols.json"
moduleName = fileNameComponents[0]
} else {
moduleName = fileName.split(separator: "@", maxSplits: 1).last.map({ String($0) })!
}
}
return (moduleName, isMainSymbolGraph)
}
}
|