|
| 1 | +//===--------------- PlaceholderDependencyResolution.swift ----------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2020 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | +import TSCBasic |
| 13 | +import TSCUtility |
| 14 | +import Foundation |
| 15 | + |
| 16 | +@_spi(Testing) public extension InterModuleDependencyGraph { |
| 17 | + // Building a Swift module in Explicit Module Build mode requires passing all of its module |
| 18 | + // dependencies as explicit arguments to the build command. |
| 19 | + // |
| 20 | + // When the driver's clients (build systems) are planning a build that involves multiple |
| 21 | + // Swift modules, planning for each individual module may take place before its dependencies |
| 22 | + // have been built. This means that the dependency scanning action will not be able to |
| 23 | + // discover such modules. In such cases, the clients must provide the driver with information |
| 24 | + // about such external dependencies, including the path to where their compiled .swiftmodule |
| 25 | + // will be located, once built, and a full inter-module dependency graph for each such dependence. |
| 26 | + // |
| 27 | + // The driver will pass down the information about such external dependencies to the scanning |
| 28 | + // action, which will generate `placeholder` swift modules for them in the resulting dependency |
| 29 | + // graph. The driver will then use the complete dependency graph provided by |
| 30 | + // the client for each external dependency and use it to "resolve" the dependency's "placeholder" |
| 31 | + // module. |
| 32 | + // |
| 33 | + // Consider an example SwiftPM package with two targets: target B, and target A, where A |
| 34 | + // depends on B: |
| 35 | + // SwiftPM will process targets in a topological order and “bubble-up” each target’s |
| 36 | + // inter-module dependency graph to its dependees. First, SwiftPM will process B, and be |
| 37 | + // able to plan its full build because it does not have any target dependencies. Then the |
| 38 | + // driver is tasked with planning a build for A. SwiftPM will pass as input to the driver |
| 39 | + // the module dependency graph of its target’s dependencies, in this case, just the |
| 40 | + // dependency graph of B. The scanning action for module A will contain a placeholder module B, |
| 41 | + // which the driver will then resolve using B's full dependency graph provided by the client. |
| 42 | + |
| 43 | + /// Resolve all placeholder dependencies using external dependency information provided by the client |
| 44 | + mutating func resolvePlaceholderDependencies(for externalBuildArtifacts: ExternalBuildArtifacts, |
| 45 | + using dependencyOracle: InterModuleDependencyOracle) |
| 46 | + throws { |
| 47 | + let externalTargetModulePathMap = externalBuildArtifacts.0 |
| 48 | + let placeholderFilter : (ModuleDependencyId) -> Bool = { |
| 49 | + if case .swiftPlaceholder(_) = $0 { |
| 50 | + return true |
| 51 | + } |
| 52 | + return false |
| 53 | + } |
| 54 | + var placeholderModules = modules.keys.filter(placeholderFilter) |
| 55 | + |
| 56 | + // Resolve all target placeholder modules |
| 57 | + let placeholderTargetModules = placeholderModules.filter { externalTargetModulePathMap[$0] != nil } |
| 58 | + for moduleId in placeholderTargetModules { |
| 59 | + guard let placeholderModulePath = externalTargetModulePathMap[moduleId] else { |
| 60 | + throw Driver.Error.missingExternalDependency(moduleId.moduleName) |
| 61 | + } |
| 62 | + try resolveTargetPlaceholder(placeholderId: moduleId, |
| 63 | + placeholderPath: placeholderModulePath, |
| 64 | + dependencyOracle: dependencyOracle) |
| 65 | + } |
| 66 | + |
| 67 | + // Process remaining placeholders until there are none left |
| 68 | + placeholderModules = modules.keys.filter(placeholderFilter) |
| 69 | + while !placeholderModules.isEmpty { |
| 70 | + let moduleId = placeholderModules.first! |
| 71 | + let swiftModuleId = ModuleDependencyId.swift(moduleId.moduleName) |
| 72 | + guard let moduleInfo = dependencyOracle.getExternalModuleInfo(of: swiftModuleId) else { |
| 73 | + throw Driver.Error.missingExternalDependency(moduleId.moduleName) |
| 74 | + } |
| 75 | + |
| 76 | + // Insert the resolved module, replacing the placeholder. |
| 77 | + try Self.mergeModule(swiftModuleId, moduleInfo, into: &modules) |
| 78 | + |
| 79 | + // Traverse and add all of this external module's dependencies to the current graph. |
| 80 | + try resolvePlaceholderModuleDependencies(moduleId: swiftModuleId, |
| 81 | + dependencyOracle: dependencyOracle) |
| 82 | + |
| 83 | + // Update the set of remaining placeholders to resolve |
| 84 | + placeholderModules = modules.keys.filter(placeholderFilter) |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +fileprivate extension InterModuleDependencyGraph { |
| 90 | + /// Resolve a placeholder dependency that is an external target. |
| 91 | + mutating func resolveTargetPlaceholder(placeholderId: ModuleDependencyId, |
| 92 | + placeholderPath: AbsolutePath, |
| 93 | + dependencyOracle: InterModuleDependencyOracle) |
| 94 | + throws { |
| 95 | + // For this placeholder dependency, generate a new module info containing only the pre-compiled |
| 96 | + // module path, and insert it into the current module's dependency graph, |
| 97 | + // replacing equivalent placeholder module. |
| 98 | + // |
| 99 | + // For all dependencies of this placeholder (direct and transitive), insert them |
| 100 | + // into this module's graph. |
| 101 | + // - Swift dependencies are inserted as-is |
| 102 | + // - Clang dependencies are inserted as-is, if a matching Clang module is already found |
| 103 | + // in this module's graph, we merge moduleInfos of the two modules, in order to obtain |
| 104 | + // a super-set of their dependencies at all possible PCMArgs variants. |
| 105 | + // |
| 106 | + // The placeholder is resolved into a .swiftPrebuiltExternal module in the dependency graph. |
| 107 | + // The placeholder's corresponding module may appear in the externalModuleInfoMap as either |
| 108 | + // a .swift module or a .swiftPrebuiltExternal module if it had been resolved earlier |
| 109 | + // in the multi-module build planning context. |
| 110 | + let swiftModuleId = ModuleDependencyId.swift(placeholderId.moduleName) |
| 111 | + let swiftPrebuiltModuleId = ModuleDependencyId.swiftPrebuiltExternal(placeholderId.moduleName) |
| 112 | + let externalModuleId: ModuleDependencyId |
| 113 | + let externalModuleInfo: ModuleInfo |
| 114 | + if let moduleInfo = dependencyOracle.getExternalModuleInfo(of: swiftModuleId) { |
| 115 | + externalModuleId = swiftModuleId |
| 116 | + externalModuleInfo = moduleInfo |
| 117 | + } else if let prebuiltModuleInfo = dependencyOracle.getExternalModuleInfo(of: swiftPrebuiltModuleId) { |
| 118 | + externalModuleId = swiftPrebuiltModuleId |
| 119 | + externalModuleInfo = prebuiltModuleInfo |
| 120 | + } else { |
| 121 | + throw Driver.Error.missingExternalDependency(placeholderId.moduleName) |
| 122 | + } |
| 123 | + |
| 124 | + let newExternalModuleDetails = |
| 125 | + SwiftPrebuiltExternalModuleDetails(compiledModulePath: placeholderPath.description) |
| 126 | + let newInfo = ModuleInfo(modulePath: placeholderPath.description, |
| 127 | + sourceFiles: [], |
| 128 | + directDependencies: externalModuleInfo.directDependencies, |
| 129 | + details: .swiftPrebuiltExternal(newExternalModuleDetails)) |
| 130 | + |
| 131 | + // Insert the resolved module, replacing the placeholder. |
| 132 | + try Self.mergeModule(swiftPrebuiltModuleId, newInfo, into: &modules) |
| 133 | + |
| 134 | + // Traverse and add all of this external target's dependencies to the current graph. |
| 135 | + try resolvePlaceholderModuleDependencies(moduleId: externalModuleId, |
| 136 | + dependencyOracle: dependencyOracle) |
| 137 | + } |
| 138 | + |
| 139 | + /// Resolve all dependencies of a placeholder module (direct and transitive), but merging them into the current graph. |
| 140 | + mutating func resolvePlaceholderModuleDependencies(moduleId: ModuleDependencyId, |
| 141 | + dependencyOracle: InterModuleDependencyOracle) |
| 142 | + throws { |
| 143 | + guard let resolvingModuleInfo = dependencyOracle.getExternalModuleInfo(of: moduleId) else { |
| 144 | + throw Driver.Error.missingExternalDependency(moduleId.moduleName) |
| 145 | + } |
| 146 | + |
| 147 | + // Breadth-first traversal of all the dependencies of this module |
| 148 | + var visited: Set<ModuleDependencyId> = [] |
| 149 | + var toVisit: [ModuleDependencyId] = resolvingModuleInfo.directDependencies ?? [] |
| 150 | + var currentIndex = 0 |
| 151 | + while let currentId = toVisit[currentIndex...].first { |
| 152 | + currentIndex += 1 |
| 153 | + visited.insert(currentId) |
| 154 | + guard let currentInfo = dependencyOracle.getExternalModuleInfo(of: currentId) else { |
| 155 | + throw Driver.Error.missingExternalDependency(currentId.moduleName) |
| 156 | + } |
| 157 | + |
| 158 | + try Self.mergeModule(currentId, currentInfo, into: &modules) |
| 159 | + |
| 160 | + let currentDependencies = currentInfo.directDependencies ?? [] |
| 161 | + for childId in currentDependencies where !visited.contains(childId) { |
| 162 | + if !toVisit.contains(childId) { |
| 163 | + toVisit.append(childId) |
| 164 | + } |
| 165 | + } |
| 166 | + } |
| 167 | + } |
| 168 | +} |
0 commit comments