|
| 1 | +//===--------------- ModuleDependencyBuildGeneration.swift ----------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 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 ModuleDependencyGraph { |
| 17 | + /// A topologically-ordered list of dependent modules to be built. |
| 18 | + nonmutating func topologicalBuildOrder() -> [ModuleDependencyId] { |
| 19 | + return topologicalBuildBatchOrder().flatMap { $0 } |
| 20 | + } |
| 21 | + |
| 22 | + /// Use Kahn's algorithm to determine the order in which the dependent |
| 23 | + /// modules should be built. |
| 24 | + nonmutating func topologicalBuildBatchOrder() -> [[ModuleDependencyId]] { |
| 25 | + var orderedModuleBatchList : [[ModuleDependencyId]] = [] |
| 26 | + // A worklist of modules and their dependencies |
| 27 | + var moduleDependenciesMap = modules.mapValues { value in Set(value.directDependencies) } |
| 28 | + // Find modules without any dependencies and remove them from the worklist |
| 29 | + var currentBatch = Array(moduleDependenciesMap.filter { $0.value.isEmpty }.keys) |
| 30 | + var nextBatch : [ModuleDependencyId] = [] |
| 31 | + currentBatch.forEach { moduleDependenciesMap.removeValue(forKey: $0) } |
| 32 | + orderedModuleBatchList.append(currentBatch) |
| 33 | + |
| 34 | + // Proceed until there are no more independent modules to be built |
| 35 | + while (!currentBatch.isEmpty) { |
| 36 | + let readyModule = currentBatch.removeFirst() |
| 37 | + // Remove dependency on `readyModule` from all other modules |
| 38 | + for key in moduleDependenciesMap.keys { |
| 39 | + var deps = moduleDependenciesMap[key] |
| 40 | + deps?.remove(readyModule) |
| 41 | + moduleDependenciesMap[key] = deps |
| 42 | + } |
| 43 | + |
| 44 | + // Add newly 'freed' modules to the the next batch |
| 45 | + for newlyIndependentModule in moduleDependenciesMap.filter({ $0.value.isEmpty }) { |
| 46 | + nextBatch.append(newlyIndependentModule.key) |
| 47 | + moduleDependenciesMap.removeValue(forKey: newlyIndependentModule.key) |
| 48 | + } |
| 49 | + |
| 50 | + // This batch is done, write it to the output and move on to the next one |
| 51 | + if (currentBatch.isEmpty && !nextBatch.isEmpty) { |
| 52 | + currentBatch = nextBatch |
| 53 | + nextBatch.removeAll() |
| 54 | + orderedModuleBatchList.append(currentBatch) |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + // If there are still modules left to build, the module dependencies |
| 59 | + // must be malformed (contain cycles). |
| 60 | + assert(moduleDependenciesMap.isEmpty, "Cycles in module dependency graph detected.") |
| 61 | + return orderedModuleBatchList |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +extension Driver { |
| 66 | + /// For the current moduleDependencyGraph, plan the order and generate jobs |
| 67 | + /// for explicitly building all dependency modules. |
| 68 | + mutating func planExplicitModuleDependenciesCompile() throws -> [Job] { |
| 69 | + var jobs: [Job] = [] |
| 70 | + guard let dependencyGraph = moduleDependencyGraph else { return jobs } |
| 71 | + let moduleBuildOrder = dependencyGraph.topologicalBuildOrder() |
| 72 | + for id in moduleBuildOrder { |
| 73 | + switch id { |
| 74 | + case .swift(let moduleName): |
| 75 | + let swiftModuleBuildJob = try genSwiftModuleDependencyBuildJob(moduleId: id, |
| 76 | + moduleName: moduleName) |
| 77 | + jobs.append(swiftModuleBuildJob) |
| 78 | + case .clang(let moduleName): |
| 79 | + let clangModuleBuildJob = try genClangModuleDependencyBuildJob(moduleId: id, |
| 80 | + moduleName: moduleName) |
| 81 | + jobs.append(clangModuleBuildJob) |
| 82 | + |
| 83 | + } |
| 84 | + } |
| 85 | + return jobs |
| 86 | + } |
| 87 | + |
| 88 | + /// For a given swift module dependency, generate a build job |
| 89 | + mutating private func genSwiftModuleDependencyBuildJob(moduleId: ModuleDependencyId, |
| 90 | + moduleName: String) throws -> Job { |
| 91 | + // FIXIT: Needs more error handling |
| 92 | + guard let moduleInfo = moduleDependencyGraph!.modules[moduleId] else { |
| 93 | + fatalError("Constructing build job for unknown module: \(moduleName)") |
| 94 | + } |
| 95 | + guard case .swift(let swiftModuleDetails) = moduleInfo.details else { |
| 96 | + fatalError("Malformed Swift Module Dependency: \(moduleName), no details object.") |
| 97 | + } |
| 98 | + |
| 99 | + var inputs: [TypedVirtualPath] = [] |
| 100 | + var outputs: [TypedVirtualPath] = [ |
| 101 | + TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath), type: .swiftModule) |
| 102 | + ] |
| 103 | + var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) } |
| 104 | + commandLine.appendFlag("-frontend") |
| 105 | + |
| 106 | + // If source files are given, compile them into a module re-using the |
| 107 | + // current compile's options |
| 108 | + if (!moduleInfo.sourceFiles.isEmpty) { |
| 109 | + commandLine.appendFlags("-emit-module", "-module-name", moduleName) |
| 110 | + commandLine.appendFlag("-emit-module-path=" + moduleInfo.modulePath) |
| 111 | + commandLine.append(contentsOf: try moduleInfo.sourceFiles.map { |
| 112 | + Job.ArgTemplate.path(try VirtualPath(path: $0))}) |
| 113 | + inputs.append(contentsOf: try moduleInfo.sourceFiles.map { |
| 114 | + TypedVirtualPath(file: try VirtualPath(path: $0), type: .swift) }) |
| 115 | + try addCommonFrontendOptions(commandLine: &commandLine) |
| 116 | + try addCommonModuleOptions(commandLine: &commandLine, outputs: &outputs) |
| 117 | + } else { |
| 118 | + // If no source files are provided, there must be a .swiftinterfaces file |
| 119 | + // and a list of command line options specified in the `details` field. |
| 120 | + guard let moduleInterfacePath = swiftModuleDetails.moduleInterfacePath else { |
| 121 | + fatalError("Malformed Swift Module Dependency: \(moduleName), no moduleInterfacePath object.") |
| 122 | + } |
| 123 | + inputs.append(TypedVirtualPath(file: try VirtualPath(path: moduleInterfacePath), |
| 124 | + type: .swiftInterface)) |
| 125 | + try addCommonModuleOptions(commandLine: &commandLine, outputs: &outputs) |
| 126 | + swiftModuleDetails.commandLine?.forEach { commandLine.appendFlag($0) } |
| 127 | + } |
| 128 | + |
| 129 | + return Job( |
| 130 | + kind: .emitModule, |
| 131 | + tool: .absolute(try toolchain.getToolPath(.swiftCompiler)), |
| 132 | + commandLine: commandLine, |
| 133 | + inputs: inputs, |
| 134 | + outputs: outputs |
| 135 | + ) |
| 136 | + } |
| 137 | + |
| 138 | + /// For a given clang module dependency, generate a build job |
| 139 | + mutating private func genClangModuleDependencyBuildJob(moduleId: ModuleDependencyId, |
| 140 | + moduleName: String) throws -> Job { |
| 141 | + // For clang modules, the Fast Dependency Scanner emits a list of source |
| 142 | + // files (with a .modulemap among them), and a list of compile command |
| 143 | + // options. |
| 144 | + // FIXIT: Needs more error handling |
| 145 | + guard var moduleInfo = moduleDependencyGraph!.modules[moduleId] else { |
| 146 | + fatalError("Constructing build job for unknown module: \(moduleName)") |
| 147 | + } |
| 148 | + guard case .clang(let clangModuleDetails) = moduleInfo.details else { |
| 149 | + fatalError("Malformed Clang Module Dependency: \(moduleName), no details object.") |
| 150 | + } |
| 151 | + var inputs: [TypedVirtualPath] = [] |
| 152 | + var outputs: [TypedVirtualPath] = [ |
| 153 | + TypedVirtualPath(file: try VirtualPath(path: moduleInfo.modulePath), type: .pcm) |
| 154 | + ] |
| 155 | + var commandLine: [Job.ArgTemplate] = swiftCompilerPrefixArgs.map { Job.ArgTemplate.flag($0) } |
| 156 | + commandLine.appendFlag("-frontend") |
| 157 | + commandLine.appendFlags("-emit-pcm", "-module-name", moduleName) |
| 158 | + |
| 159 | + // The .modulemap is currently duplicated in moduleMapPath and in sourceFiles. |
| 160 | + // It also must be the very first input argument to the compile command. |
| 161 | + if let index = moduleInfo.sourceFiles.firstIndex(of: clangModuleDetails.moduleMapPath) { |
| 162 | + moduleInfo.sourceFiles.swapAt(0, index) |
| 163 | + } |
| 164 | + commandLine.append(contentsOf: try moduleInfo.sourceFiles.map { |
| 165 | + Job.ArgTemplate.path(try VirtualPath(path: $0))}) |
| 166 | + inputs.append(contentsOf: try moduleInfo.sourceFiles.map { |
| 167 | + TypedVirtualPath(file: try VirtualPath(path: $0), type: .swift) }) |
| 168 | + try addCommonModuleOptions(commandLine: &commandLine, outputs: &outputs) |
| 169 | + clangModuleDetails.commandLine?.forEach { commandLine.appendFlags("-Xcc", $0) } |
| 170 | + |
| 171 | + return Job( |
| 172 | + kind: .generatePCM, |
| 173 | + tool: .absolute(try toolchain.getToolPath(.swiftCompiler)), |
| 174 | + commandLine: commandLine, |
| 175 | + inputs: inputs, |
| 176 | + outputs: outputs |
| 177 | + ) |
| 178 | + } |
| 179 | +} |
0 commit comments