|
| 1 | +import TSCBasic |
| 2 | +import TSCUtility |
| 3 | +import Foundation |
| 4 | + |
| 5 | +// FIXME: rename to something like IncrementalCompilationInitialState |
| 6 | +public struct IncrementalCompilation { |
| 7 | + public let showIncrementalBuildDecisions: Bool |
| 8 | + public let shouldCompileIncrementally: Bool |
| 9 | + public let buildRecordPath: VirtualPath? |
| 10 | + public let outputBuildRecordForModuleOnlyBuild: Bool |
| 11 | + public let argsHash: String |
| 12 | + public let lastBuildTime: Date |
| 13 | + public let outOfDateMap: InputInfoMap? |
| 14 | + public let rebuildEverything: Bool |
| 15 | + |
| 16 | + public init(_ parsedOptions: inout ParsedOptions, |
| 17 | + compilerMode: CompilerMode, |
| 18 | + outputFileMap: OutputFileMap?, |
| 19 | + compilerOutputType: FileType?, |
| 20 | + moduleOutput: ModuleOutput?, |
| 21 | + inputFiles: [TypedVirtualPath], |
| 22 | + diagnosticEngine: DiagnosticsEngine |
| 23 | + ) { |
| 24 | + let showIncrementalBuildDecisions = Self.getShowIncrementalBuildDecisions(&parsedOptions) |
| 25 | + self.showIncrementalBuildDecisions = showIncrementalBuildDecisions |
| 26 | + |
| 27 | + let shouldCompileIncrementally = Self.computeAndExplainShouldCompileIncrementally( |
| 28 | + &parsedOptions, |
| 29 | + showIncrementalBuildDecisions: showIncrementalBuildDecisions, |
| 30 | + compilerMode: compilerMode) |
| 31 | + |
| 32 | + self.shouldCompileIncrementally = shouldCompileIncrementally |
| 33 | + |
| 34 | + self.buildRecordPath = Self.computeBuildRecordPath( |
| 35 | + outputFileMap: outputFileMap, |
| 36 | + compilerOutputType: compilerOutputType, |
| 37 | + diagnosticEngine: shouldCompileIncrementally ? diagnosticEngine : nil) |
| 38 | + |
| 39 | + // If we emit module along with full compilation, emit build record |
| 40 | + // file for '-emit-module' only mode as well. |
| 41 | + self.outputBuildRecordForModuleOnlyBuild = self.buildRecordPath != nil && |
| 42 | + moduleOutput?.isTopLevel ?? false |
| 43 | + |
| 44 | + let argsHash = Self.computeArgsHash(parsedOptions) |
| 45 | + self.argsHash = argsHash |
| 46 | + let lastBuildTime = Date.init() |
| 47 | + self.lastBuildTime = lastBuildTime |
| 48 | + |
| 49 | + if let buRP = buildRecordPath, shouldCompileIncrementally { |
| 50 | + self.outOfDateMap = InputInfoMap.populateOutOfDateMap( |
| 51 | + argsHash: argsHash, |
| 52 | + lastBuildTime: lastBuildTime, |
| 53 | + inputFiles: inputFiles, |
| 54 | + buildRecordPath: buRP, |
| 55 | + showIncrementalBuildDecisions: showIncrementalBuildDecisions) |
| 56 | + } |
| 57 | + else { |
| 58 | + self.outOfDateMap = nil |
| 59 | + } |
| 60 | + // FIXME: Distinguish errors from "file removed", which is benign. |
| 61 | + self.rebuildEverything = outOfDateMap == nil |
| 62 | + } |
| 63 | + |
| 64 | + private static func getShowIncrementalBuildDecisions(_ parsedOptions: inout ParsedOptions) |
| 65 | + -> Bool { |
| 66 | + parsedOptions.hasArgument(.driver_show_incremental) |
| 67 | + } |
| 68 | + |
| 69 | + private static func computeAndExplainShouldCompileIncrementally( |
| 70 | + _ parsedOptions: inout ParsedOptions, |
| 71 | + showIncrementalBuildDecisions: Bool, |
| 72 | + compilerMode: CompilerMode |
| 73 | + ) |
| 74 | + -> Bool |
| 75 | + { |
| 76 | + func explain(disabledBecause why: String) { |
| 77 | + stdoutStream <<< "Incremental compilation has been disabled, because it \(why).\n" |
| 78 | + stdoutStream.flush() |
| 79 | + } |
| 80 | + guard parsedOptions.hasArgument(.incremental) else { |
| 81 | + return false |
| 82 | + } |
| 83 | + guard compilerMode.supportsIncrementalCompilation else { |
| 84 | + explain(disabledBecause: "is not compatible with \(compilerMode)") |
| 85 | + return false |
| 86 | + } |
| 87 | + guard !parsedOptions.hasArgument(.embed_bitcode) else { |
| 88 | + explain(disabledBecause: "is not currently compatible with embedding LLVM IR bitcode") |
| 89 | + return false |
| 90 | + } |
| 91 | + return true |
| 92 | + } |
| 93 | + |
| 94 | + private static func computeBuildRecordPath( |
| 95 | + outputFileMap: OutputFileMap?, |
| 96 | + compilerOutputType: FileType?, |
| 97 | + diagnosticEngine: DiagnosticsEngine? |
| 98 | + ) -> VirtualPath? { |
| 99 | + // FIXME: This should work without an output file map. We should have |
| 100 | + // another way to specify a build record and where to put intermediates. |
| 101 | + guard let ofm = outputFileMap else { |
| 102 | + diagnosticEngine.map { $0.emit(.warning_incremental_requires_output_file_map) } |
| 103 | + return nil |
| 104 | + } |
| 105 | + guard let partialBuildRecordPath = ofm.existingOutputForSingleInput(outputType: .swiftDeps) |
| 106 | + else { |
| 107 | + diagnosticEngine.map { $0.emit(.warning_incremental_requires_build_record_entry) } |
| 108 | + return nil |
| 109 | + } |
| 110 | + // In 'emit-module' only mode, use build-record filename suffixed with |
| 111 | + // '~moduleonly'. So that module-only mode doesn't mess up build-record |
| 112 | + // file for full compilation. |
| 113 | + return try! compilerOutputType == .swiftModule |
| 114 | + ? VirtualPath(path: partialBuildRecordPath.name + "~moduleonly") |
| 115 | + : partialBuildRecordPath |
| 116 | + } |
| 117 | + |
| 118 | + static private func computeArgsHash(_ parsedOptionsArg: ParsedOptions) -> String { |
| 119 | + var parsedOptions = parsedOptionsArg |
| 120 | + let hashInput = parsedOptions |
| 121 | + .filter { $0.option.affectsIncrementalBuild && $0.option.kind != .input} |
| 122 | + .map {$0.option.spelling} |
| 123 | + .sorted() |
| 124 | + .joined() |
| 125 | + return SHA256(hashInput).digestString() |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | + |
| 130 | +fileprivate extension CompilerMode { |
| 131 | + var supportsIncrementalCompilation: Bool { |
| 132 | + switch self { |
| 133 | + case .standardCompile, .immediate, .repl: return true |
| 134 | + case .singleCompile: return false |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +public extension Diagnostic.Message { |
| 140 | + static var warning_incremental_requires_output_file_map: Diagnostic.Message { |
| 141 | + .warning("ignoring -incremental (currently requires an output file map)") |
| 142 | + } |
| 143 | + static var warning_incremental_requires_build_record_entry: Diagnostic.Message { |
| 144 | + .warning( |
| 145 | + "ignoring -incremental; " + |
| 146 | + "output file map has no master dependencies entry under \(FileType.swiftDeps)" |
| 147 | + ) |
| 148 | + } |
| 149 | +} |
0 commit comments