File: BuildRecordInfo.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (234 lines) | stat: -rw-r--r-- 8,392 bytes parent folder | download
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//===--------------- BuildRecordInfo.swift --------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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 the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import struct Foundation.Data
import class Foundation.JSONDecoder

import class TSCBasic.DiagnosticsEngine
import protocol TSCBasic.FileSystem
import struct TSCBasic.AbsolutePath
import struct TSCBasic.ByteString
import struct TSCBasic.ProcessResult
import struct TSCBasic.SHA256

import SwiftOptions
import class Dispatch.DispatchQueue

/// Holds information required to read and write the build record (aka
/// compilation record).
///
/// This info is always written, but only read for incremental compilation.
@_spi(Testing) public final class BuildRecordInfo {
  /// A pair of a `Job` and the `ProcessResult` corresponding to the outcome of
  /// its execution during this compilation session.
  struct JobResult {
    /// The job that was executed.
    var job: Job
    /// The result of executing the associated `job`.
    var result: ProcessResult

    init(_ j: Job, _ result: ProcessResult) {
      self.job = j
      self.result = result
    }
  }

  let buildRecordPath: VirtualPath
  let fileSystem: FileSystem
  let currentArgsHash: String
  @_spi(Testing) public let actualSwiftVersion: String
  @_spi(Testing) public let timeBeforeFirstJob: TimePoint
  let diagnosticEngine: DiagnosticsEngine
  let compilationInputModificationDates: [TypedVirtualPath: TimePoint]
  private var explicitModuleDependencyGraph: InterModuleDependencyGraph? = nil

  private var finishedJobResults = [JobResult]()
  // A confinement queue that protects concurrent access to the
  // `finishedJobResults` array.
  // FIXME: Use an actor when possible.
  private let confinementQueue = DispatchQueue(label: "com.apple.swift-driver.jobresults")

  @_spi(Testing) public init(
    buildRecordPath: VirtualPath,
    fileSystem: FileSystem,
    currentArgsHash: String,
    actualSwiftVersion: String,
    timeBeforeFirstJob: TimePoint,
    diagnosticEngine: DiagnosticsEngine,
    compilationInputModificationDates: [TypedVirtualPath: TimePoint])
  {
    self.buildRecordPath = buildRecordPath
    self.fileSystem = fileSystem
    self.currentArgsHash = currentArgsHash
    self.actualSwiftVersion = actualSwiftVersion
    self.timeBeforeFirstJob = timeBeforeFirstJob
    self.diagnosticEngine = diagnosticEngine
    self.compilationInputModificationDates = compilationInputModificationDates
  }

  convenience init?(
    actualSwiftVersion: String,
    compilerOutputType: FileType?,
    workingDirectory: AbsolutePath?,
    diagnosticEngine: DiagnosticsEngine,
    fileSystem: FileSystem,
    moduleOutputInfo: ModuleOutputInfo,
    outputFileMap: OutputFileMap?,
    incremental: Bool,
    parsedOptions: ParsedOptions,
    recordedInputModificationDates: [TypedVirtualPath: TimePoint]
  ) {
    // Cannot write a buildRecord without a path.
    guard let buildRecordPath = try? Self.computeBuildRecordPath(
            outputFileMap: outputFileMap,
            incremental: incremental,
            compilerOutputType: compilerOutputType,
            workingDirectory: workingDirectory,
            diagnosticEngine: diagnosticEngine)
    else {
      return nil
    }
    let currentArgsHash = Self.computeArgsHash(parsedOptions)
    let compilationInputModificationDates =
      recordedInputModificationDates.filter { input, _ in
        input.type.isPartOfSwiftCompilation
      }

    self.init(
      buildRecordPath: buildRecordPath,
      fileSystem: fileSystem,
      currentArgsHash: currentArgsHash,
      actualSwiftVersion: actualSwiftVersion,
      timeBeforeFirstJob: .now(),
      diagnosticEngine: diagnosticEngine,
      compilationInputModificationDates: compilationInputModificationDates)
   }

  private static func computeArgsHash(_ parsedOptionsArg: ParsedOptions
  ) -> String {
    var parsedOptions = parsedOptionsArg
    let hashInput = parsedOptions
      .filter { $0.option.affectsIncrementalBuild && $0.option.kind != .input}
      .map { $0.description } // The description includes the spelling of the option itself and, if present, its argument(s).
      .joined()
    return SHA256().hash(hashInput).hexadecimalRepresentation
  }

  /// Determine the input and output path for the build record
  private static func computeBuildRecordPath(
    outputFileMap: OutputFileMap?,
    incremental: Bool,
    compilerOutputType: FileType?,
    workingDirectory: AbsolutePath?,
    diagnosticEngine: DiagnosticsEngine
  ) throws -> VirtualPath? {
    // FIXME: This should work without an output file map. We should have
    // another way to specify a build record and where to put intermediates.
    guard let ofm = outputFileMap else {
      return nil
    }
    guard let partialBuildRecordPath =
            try ofm.existingOutputForSingleInput(outputType: .swiftDeps)
    else {
      if incremental {
        diagnosticEngine.emit(.warning_incremental_requires_build_record_entry)
      }
      return nil
    }
    return workingDirectory
      .map(VirtualPath.lookup(partialBuildRecordPath).resolvedRelativePath(base:))
      ?? VirtualPath.lookup(partialBuildRecordPath)
  }

  /// Write out the build record.
  ///
  /// - Parameters:
  ///   - jobs: All compilation jobs formed during this build.
  ///   - skippedInputs: All primary inputs that were not compiled because the
  ///                    incremental build plan determined they could be
  ///                    skipped.
  @_spi(Testing) public func buildRecord(_ jobs: [Job], _ skippedInputs: Set<TypedVirtualPath>?) -> BuildRecord {
    return self.confinementQueue.sync {
      BuildRecord(
        jobs: jobs,
        finishedJobResults: finishedJobResults,
        skippedInputs: skippedInputs,
        compilationInputModificationDates: compilationInputModificationDates,
        actualSwiftVersion: actualSwiftVersion,
        argsHash: currentArgsHash,
        timeBeforeFirstJob: timeBeforeFirstJob,
        timeAfterLastJob: .now())
    }
  }

  func removeBuildRecord() {
    guard let absPath = buildRecordPath.absolutePath else {
      return
    }
    try? fileSystem.removeFileTree(absPath)
  }

  func removeInterModuleDependencyGraph() {
    guard let absPath = interModuleDependencyGraphPath.absolutePath else {
      return
    }
    try? fileSystem.removeFileTree(absPath)
  }

  func readPriorInterModuleDependencyGraph(
    reporter: IncrementalCompilationState.Reporter?
  ) -> InterModuleDependencyGraph? {
    let decodedGraph: InterModuleDependencyGraph
    do {
      let contents = try fileSystem.readFileContents(interModuleDependencyGraphPath).cString
      decodedGraph = try JSONDecoder().decode(InterModuleDependencyGraph.self,
                                              from: Data(contents.utf8))
    } catch {
      return nil
    }
    reporter?.report("Read inter-module dependency graph", interModuleDependencyGraphPath)

    return decodedGraph
  }

  func jobFinished(job: Job, result: ProcessResult) {
    self.confinementQueue.sync {
      finishedJobResults.append(JobResult(job, result))
    }
  }

  /// A build-record-relative path to the location of a serialized copy of the
  /// driver's dependency graph.
  ///
  /// FIXME: This is a little ridiculous. We could probably just replace the
  /// build record outright with a serialized format.
  @_spi(Testing) public var dependencyGraphPath: VirtualPath {
    let filename = buildRecordPath.basenameWithoutExt
    return buildRecordPath
      .parentDirectory
      .appending(component: filename + ".priors")
  }

  /// A build-record-relative path to the location of a serialized copy of the
  /// driver's inter-module dependency graph.
  var interModuleDependencyGraphPath: VirtualPath {
    let filename = buildRecordPath.basenameWithoutExt
    return buildRecordPath
      .parentDirectory
      .appending(component: filename + ".moduledeps")
  }

  /// Directory to emit dot files into
  var dotFileDirectory: VirtualPath {
    buildRecordPath.parentDirectory
  }
}