|
| 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 | +extension ExplicitModuleBuildHandler { |
| 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 public func resolvePlaceholderDependencies() throws { |
| 45 | + let placeholderModules = dependencyGraph.modules.keys.filter { |
| 46 | + if case .swiftPlaceholder(_) = $0 { |
| 47 | + return true |
| 48 | + } |
| 49 | + return false |
| 50 | + } |
| 51 | + |
| 52 | + // Resolve all placeholder modules |
| 53 | + for moduleId in placeholderModules { |
| 54 | + guard let (placeholderModulePath, placeholderDependencyGraph) = |
| 55 | + externalDependencyArtifactMap[moduleId] else { |
| 56 | + throw Driver.Error.missingExternalDependency(moduleId.moduleName) |
| 57 | + } |
| 58 | + try resolvePlaceholderDependency(placeholderModulePath: placeholderModulePath, |
| 59 | + placeholderDependencyGraph: placeholderDependencyGraph) |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + /// Merge a given external module's dependency graph in place of a placeholder dependency |
| 64 | + mutating public func resolvePlaceholderDependency(placeholderModulePath: AbsolutePath, |
| 65 | + placeholderDependencyGraph: InterModuleDependencyGraph) |
| 66 | + throws { |
| 67 | + // For every other module in the placeholder dependency graph, generate a new module info |
| 68 | + // containing only the pre-compiled module path, and insert it into the current module's |
| 69 | + // dependency graph, replacing equivalent (non pre-built) modules, if necessary. |
| 70 | + for (moduleId, moduleInfo) in placeholderDependencyGraph.modules { |
| 71 | + switch moduleId { |
| 72 | + case .swift(_): |
| 73 | + // Compute the compiled module path for this module. |
| 74 | + // If this module is the placeholder itself, this information was passed from SwiftPM |
| 75 | + // If this module is any other swift module, then the compiled module path is |
| 76 | + // a part of the details field. |
| 77 | + // Otherwise (for most other dependencies), it is the modulePath of the moduleInfo node. |
| 78 | + // FIXME: Will the `else` case ever happen? |
| 79 | + let compiledModulePath : String |
| 80 | + if moduleId.moduleName == placeholderDependencyGraph.mainModuleName { |
| 81 | + compiledModulePath = placeholderModulePath.description |
| 82 | + } else if case .swift(let details) = moduleInfo.details, |
| 83 | + let explicitModulePath = details.explicitCompiledModulePath { |
| 84 | + compiledModulePath = explicitModulePath |
| 85 | + } else { |
| 86 | + compiledModulePath = moduleInfo.modulePath.description |
| 87 | + } |
| 88 | + |
| 89 | + let swiftDetails = |
| 90 | + SwiftModuleDetails(compiledModulePath: compiledModulePath) |
| 91 | + print("For module: \(moduleId.moduleName) set the path to:\n\(compiledModulePath)") |
| 92 | + let newInfo = ModuleInfo(modulePath: moduleInfo.modulePath.description, |
| 93 | + sourceFiles: nil, |
| 94 | + directDependencies: moduleInfo.directDependencies, |
| 95 | + details: ModuleInfo.Details.swift(swiftDetails)) |
| 96 | + try insertOrReplaceModule(moduleId: moduleId, moduleInfo: newInfo) |
| 97 | + print("New Info: \(newInfo)") |
| 98 | + case .clang(_): |
| 99 | + // Because PCM modules file names encode the specific pcmArguments of their dependees, |
| 100 | + // we cannot use pre-built files here because we do not always know which target |
| 101 | + // they corrspond to, nor do we have a way to map from a certain target to a specific |
| 102 | + // pcm file. Because of this, all PCM dependencies, direct and transitive, have to be |
| 103 | + // built for all modules. |
| 104 | + if dependencyGraph.modules[moduleId] == nil { |
| 105 | + dependencyGraph.modules[moduleId] = moduleInfo |
| 106 | + } |
| 107 | + case .swiftPlaceholder(_): |
| 108 | + try insertOrReplaceModule(moduleId: moduleId, moduleInfo: moduleInfo) |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + /// Insert a module into the handler's dependency graph. If a module with this identifier already exists, |
| 114 | + /// replace it's module with a moduleInfo that contains a path to an existing prebuilt .swiftmodule |
| 115 | + mutating public func insertOrReplaceModule(moduleId: ModuleDependencyId, |
| 116 | + moduleInfo: ModuleInfo) throws { |
| 117 | + // Check for placeholders to be replaced |
| 118 | + if dependencyGraph.modules[ModuleDependencyId.swiftPlaceholder(moduleId.moduleName)] != nil { |
| 119 | + try replaceModule(originalId: .swiftPlaceholder(moduleId.moduleName), replacementId: moduleId, |
| 120 | + replacementInfo: moduleInfo) |
| 121 | + } |
| 122 | + // Check for modules with the same Identifier, and replace if found |
| 123 | + else if dependencyGraph.modules[moduleId] != nil { |
| 124 | + try replaceModule(originalId: moduleId, replacementId: moduleId, replacementInfo: moduleInfo) |
| 125 | + // This module is new to the current dependency graph |
| 126 | + } else { |
| 127 | + dependencyGraph.modules[moduleId] = moduleInfo |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + /// Replace a module with a new one. Replace all references to the original module in other modules' dependencies |
| 132 | + /// with the new module. |
| 133 | + mutating public func replaceModule(originalId: ModuleDependencyId, |
| 134 | + replacementId: ModuleDependencyId, |
| 135 | + replacementInfo: ModuleInfo) throws { |
| 136 | + dependencyGraph.modules.removeValue(forKey: originalId) |
| 137 | + dependencyGraph.modules[replacementId] = replacementInfo |
| 138 | + for moduleId in dependencyGraph.modules.keys { |
| 139 | + var moduleInfo = dependencyGraph.modules[moduleId]! |
| 140 | + // Skip over other placeholders, they do not have dependencies |
| 141 | + if case .swiftPlaceholder(_) = moduleId { |
| 142 | + continue |
| 143 | + } |
| 144 | + if let originalModuleIndex = moduleInfo.directDependencies?.firstIndex(of: originalId) { |
| 145 | + moduleInfo.directDependencies![originalModuleIndex] = replacementId; |
| 146 | + } |
| 147 | + dependencyGraph.modules[moduleId] = moduleInfo |
| 148 | + } |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +/// Used for creating new module infos during placeholder dependency resolution |
| 153 | +/// Modules created this way only contain a path to a pre-built module file. |
| 154 | +extension SwiftModuleDetails { |
| 155 | + public init(compiledModulePath: String) { |
| 156 | + self.moduleInterfacePath = nil |
| 157 | + self.compiledModuleCandidates = nil |
| 158 | + self.explicitCompiledModulePath = compiledModulePath |
| 159 | + self.bridgingHeaderPath = nil |
| 160 | + self.bridgingSourceFiles = nil |
| 161 | + self.commandLine = nil |
| 162 | + self.extraPcmArgs = nil |
| 163 | + } |
| 164 | +} |
0 commit comments