File: Module.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 (249 lines) | stat: -rw-r--r-- 8,608 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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//===--------------- PhasedModule.swift - Swift Testing -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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 TSCBasic

@_spi(Testing) import SwiftDriver
import SwiftOptions
import TestUtilities
import XCTest

/// Represents a module to be compiled.
/// Thus, stores everything needed to invoke the compiler, and how to invoke the compiler.
public struct Module {
  /// Does this module produce an executable applications or a library?
  public enum Product {
    case executable, library
  }
  /// The name, used for files and imports.
  /// Two modules may have the same  name. You might do this to test a situation where the user adds
  /// a file to a module. Both would  have the same name, but different sources.  One `Step` would compile
  /// one module, and the next would compile the module with the additional  `Source`.
  public let name: String

  /// The `Source`s to be compiled when building this module.
  public let sources: [Source]

  /// The `Module`s imported by this module.
  let imports: [Module]

  /// What kind of thing this module produces.
  let product: Product

  /// A handy internal cache.
  /// E.g. "foo.swift: <source named: "foo">
  private let sourceMap: [String: Source]

  /// Create a `Module`
  /// - Parameters:
  ///   - named: The name to be used for directories, and imports.
  ///   - containing: The sources to be compiled.
  ///   - importing: The modules this one imports
  ///   - producing: The kind of thing produced.
  /// - Returns: A new `Module`
  public init(named name: String,
              containing sources: [Source],
              importing imports: [Module] = [],
              producing product: Product) {
    self.name = name
    self.sources = sources
    self.imports = imports
    self.product = product
    self.sourceMap = sources.spm_createDictionary { source in (source.name + ".swift", source)}
  }

  /// Compile this module
  /// - Parameters:
  ///   - addOns: The additional code to send to the compiler.
  ///   - in: The context
  /// - Returns: The `Source`s actually compiled.
  func compile(addOns: [AddOn], in context: Context) throws -> [Source] {
    if context.verbose {
      print("\n*** compiling \(name) ***")
    }
    try createFiles(adding: addOns, in: context)
    let compiledBasenames = try invokeDriver(in: context)
    return try sources(for: compiledBasenames)
  }
}
// MARK: - creating files
extension Module {

  private func createFiles(adding addOns: [AddOn], in context: Context) throws {
    try createOrRemoveSources(adding: addOns, in: context)
    try createDerivedDataDirIfMissing(in: context)
    createOutputFileMap(in: context)
  }
  /// Since the source directory may have been previously populated by a module with the same name but
  /// different `sources`, old ones must be removed, new ones added, and changed ones changes.
  private func createOrRemoveSources(adding addOns: [AddOn], in context: Context) throws {
    let dir = context.sourceRoot(for: self)
    func createSourceDir() throws {
      if !localFileSystem.exists(dir) {
        try localFileSystem.createDirectory(dir, recursive: true)
      }
    }
    func removeUnwantedSources() throws {
      for existingBasename in try localFileSystem.getDirectoryContents(dir)
      where existingBasename.hasSuffix(".swift") && sourceMap[existingBasename] == nil {
        try localFileSystem.removeFileTree(dir.appending(component: existingBasename))
      }
    }
    func addNeededSources() throws {
      for source in sources {
        let contentsWithAdditions = addOns.adjust(source.contents)
        try localFileSystem.writeIfChanged(path: context.swiftFilePath(for: source, in: self),
                                           bytes: ByteString(encodingAsUTF8: contentsWithAdditions))
      }
    }
    try createSourceDir()
    try removeUnwantedSources()
    Thread.sleep(forTimeInterval: 1)
    try addNeededSources()
  }

  private func createDerivedDataDirIfMissing(in context: Context) throws {
    let dir = context.buildRoot(for: self)
    if !localFileSystem.exists(dir) {
      try localFileSystem.createDirectory(dir, recursive: true)
    }
  }

  private func createOutputFileMap(in context: Context) {
    OutputFileMapCreator.write(
      module: name,
      inputPaths: sources.map {context.swiftFilePath(for: $0, in: self)},
      derivedData: context.buildRoot(for: self),
      to: context.outputFileMapPath(for: self))
  }
}

// MARK: - invoking the driver
extension Module {
  /// Invoke the driver to perform the compilation.
  /// - Returns: the basenames of recompiled source files.
  private func invokeDriver(in context: Context) throws -> [String] {
    var collector = CompiledSourceCollector()
    let handlers = [
      {collector.handle(diagnostic: $0)},
      context.verbose ? Driver.stderrDiagnosticsHandler : nil
    ].compactMap { $0 }
    let diagnosticsEngine = DiagnosticsEngine(handlers: handlers)

    let args = try arguments(in: context)
    var driver = try Driver(args: args, diagnosticsEngine: diagnosticsEngine)
    let jobs = try driver.planBuild()
    try driver.run(jobs: jobs)

    return collector.compiledBasenames
  }

  /// - Returns the arguments to pass to the `Driver`.
  private func arguments(in context: Context) throws -> [String] {
    let boilerPlateArgs = [
      "swiftc",
      "-no-color-diagnostics",
      "-incremental",
      "-driver-show-incremental",
      "-driver-show-job-lifecycle"]

    var searchPaths: [String] {
      let swiftModules = self.imports.map {
        context.swiftmodulePath(for: $0).parentDirectory.pathString
      }
      return swiftModules.flatMap { ["-I", $0, "-F", $0] }
        + ["-o", context.executablePath(for: self).pathString]
    }

    var libraryArgs: [String] {
      [
        "-c",
        "-parse-as-library",
        "-emit-module-path", context.swiftmodulePath(for: self).pathString,
      ] + searchPaths
    }

    var importedObjs: [String] {
      self.imports.flatMap { `import` in
        `import`.sources.map { source in
          context.objectFilePath(for: source, in: `import`).pathString
        }
      } .flatMap { ["-Xlinker", $0] }
    }

    var incrementalImportsArgs: [String] {
      switch context.incrementalImports {
      case .enabled:
        return ["-enable-incremental-imports"]
      case .disabled:
        return ["-disable-incremental-imports"]
      }
    }

    let sdkArguments = try? Driver.sdkArgumentsForTesting()

    let interestingArgs = [
      ["-module-name", self.name],
      ["-output-file-map", context.outputFileMapPath(for: self).pathString],
      incrementalImportsArgs,
      self.product == .library ? libraryArgs : searchPaths,
      sources.map { context.swiftFilePath(for: $0, in: self).pathString },
      importedObjs,
      sdkArguments ?? [],
    ].joined()

    if context.verbose {
      let withoutRootDir = interestingArgs
        .map {$0.replacingOccurrences(of: context.rootDir.pathString, with: "<rootDir>")}
      print("abridged arguments: ", withoutRootDir.joined(separator: " "), "\n")
    }

    return boilerPlateArgs + interestingArgs
  }

  func run(step: Step, in context: Context) throws -> ProcessResult? {
    let proc = Process(arguments: [context.executablePath(for: self).pathString])
    try proc.launch()
    return try proc.waitUntilExit()
  }
}
// MARK: - Reporting
extension Module {
  enum Errors: LocalizedError {
    case unexpectedCompilation(Module, String)

    var errorDescription: String? {
      switch self {
      case let .unexpectedCompilation(module, basename):
        return "\(module.name) compiled non-member \(basename).swift"
      }
    }
  }

  /// Translate source file base names to `Source` objects.
  func sources(for basenames: [String]) throws -> [Source] {
    try basenames.map { basename in
      guard let source = sourceMap[basename] else {
        throw Errors.unexpectedCompilation(self, basename)
      }
      return source
    }
  }
}

public extension Array where Element == Module {
  var allSources: [Source] {
    Set(flatMap {$0.sources}).sorted()
  }
  var allSourcesToCompile: ExpectedCompilations {
    ExpectedCompilations(allSourcesOf: self)
  }
}