Skip to content

Commit 9ef58e3

Browse files
committed
Filelist support
1 parent 9fa3a08 commit 9ef58e3

20 files changed

+459
-147
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public struct Driver {
1919
public enum Error: Swift.Error, Equatable, DiagnosticData {
2020
case invalidDriverName(String)
2121
case invalidInput(String)
22+
case invalidArgumentValue(String, String)
2223
case noJobsPassedToDriverFromEmptyInputFileList
2324
case relativeFrontendPath(String)
2425
case subcommandPassedToDriver
@@ -40,6 +41,8 @@ public struct Driver {
4041
return "invalid driver name: \(driverName)"
4142
case .invalidInput(let input):
4243
return "invalid input: \(input)"
44+
case .invalidArgumentValue(let option, let value):
45+
return "invalid value '\(value)' in '\(option)'"
4346
case .noJobsPassedToDriverFromEmptyInputFileList:
4447
return "no input files"
4548
case .relativeFrontendPath(let path):
@@ -124,6 +127,15 @@ public struct Driver {
124127
/// The mapping from input files to output files for each kind.
125128
internal let outputFileMap: OutputFileMap?
126129

130+
/// The number of files required before making a file list.
131+
internal let fileListThreshold: Int
132+
133+
/// Should use file lists for inputs (number of inputs exceeds `fileListThreshold`).
134+
public let shouldUseInputFileList: Bool
135+
136+
/// VirtualPath for shared all sources file list. `nil` if unused.
137+
public let allSourcesFileList: VirtualPath?
138+
127139
/// The mode in which the compiler will execute.
128140
public let compilerMode: CompilerMode
129141

@@ -263,7 +275,6 @@ public struct Driver {
263275
throw Error.subcommandPassedToDriver
264276
}
265277

266-
267278
var args = try Self.expandResponseFiles(args, fileSystem: fileSystem, diagnosticsEngine: self.diagnosticEngine)
268279

269280
self.driverKind = try Self.determineDriverKind(args: &args)
@@ -320,6 +331,16 @@ public struct Driver {
320331
self.outputFileMap = outputFileMap
321332
}
322333

334+
self.fileListThreshold = try Self.computeFileListThreshold(&self.parsedOptions, diagnosticsEngine: diagnosticsEngine)
335+
self.shouldUseInputFileList = inputFiles.count > fileListThreshold
336+
if shouldUseInputFileList {
337+
let swiftInputs = inputFiles.filter(\.type.isPartOfSwiftCompilation)
338+
let path = RelativePath(createTemporaryFileName(prefix: "sources"))
339+
self.allSourcesFileList = .fileList(path, .list(swiftInputs.map(\.file)))
340+
} else {
341+
self.allSourcesFileList = nil
342+
}
343+
323344
// Figure out the primary outputs from the driver.
324345
(self.compilerOutputType, self.linkerOutputType) = Self.determinePrimaryOutputs(&parsedOptions, driverKind: driverKind, diagnosticsEngine: diagnosticEngine)
325346

@@ -802,6 +823,37 @@ extension Driver {
802823
}
803824
}
804825

826+
extension Driver {
827+
private static func computeFileListThreshold(
828+
_ parsedOptions: inout ParsedOptions,
829+
diagnosticsEngine: DiagnosticsEngine
830+
) throws -> Int {
831+
let hasUseFileLists = parsedOptions.hasArgument(.driverUseFilelists)
832+
833+
if hasUseFileLists {
834+
diagnosticsEngine.emit(.warn_use_filelists_deprecated)
835+
}
836+
837+
if let threshold = parsedOptions.getLastArgument(.driverFilelistThreshold)?.asSingle {
838+
if let thresholdInt = Int(threshold) {
839+
return thresholdInt
840+
} else {
841+
throw Error.invalidArgumentValue(Option.driverFilelistThreshold.spelling, threshold)
842+
}
843+
} else if hasUseFileLists {
844+
return 0
845+
}
846+
847+
return 128
848+
}
849+
}
850+
851+
private extension Diagnostic.Message {
852+
static var warn_use_filelists_deprecated: Diagnostic.Message {
853+
.warning("the option '-driver-use-filelists' is deprecated; use '-driver-filelist-threshold=0' instead")
854+
}
855+
}
856+
805857
extension Driver {
806858
/// Compute the compiler mode based on the options.
807859
private static func computeCompilerMode(
@@ -1730,7 +1782,7 @@ extension Driver {
17301782
return path
17311783
}
17321784

1733-
return path.parentDirectory.appending(component: "\(moduleName).\(type.rawValue)")
1785+
return path.replacingExtension(with: type)
17341786
}
17351787

17361788
return try VirtualPath(path: moduleName.appendingFileTypeExtension(type))
@@ -1829,15 +1881,12 @@ extension Driver {
18291881
_ = parsedOptions.hasArgument(isOutput)
18301882
}
18311883

1832-
var parentPath: VirtualPath
18331884
if let projectDirectory = projectDirectory {
18341885
// If the build system has created a Project dir for us to include the file, use it.
1835-
parentPath = projectDirectory
1836-
} else {
1837-
parentPath = moduleOutputPath.parentDirectory
1886+
return projectDirectory.appending(component: moduleName.appendingFileTypeExtension(type))
18381887
}
18391888

1840-
return try parentPath.appending(component: moduleName).replacingExtension(with: type)
1889+
return moduleOutputPath.replacingExtension(with: type)
18411890
}
18421891

18431892
// If the output option was not provided, don't produce this output at all.

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import Foundation
1515
public typealias FileSystem = TSCBasic.FileSystem
1616

1717
/// Mapping of input file paths to specific output files.
18-
public struct OutputFileMap: Equatable {
18+
public struct OutputFileMap: Hashable, Codable {
19+
static let singleInputKey = VirtualPath.relative(RelativePath(""))
20+
1921
/// The known mapping from input file to specific output files.
2022
public var entries: [VirtualPath : [FileType : VirtualPath]] = [:]
2123

@@ -46,14 +48,14 @@ public struct OutputFileMap: Equatable {
4648
}
4749

4850
public func existingOutputForSingleInput(outputType: FileType) -> VirtualPath? {
49-
try! existingOutput(inputFile: VirtualPath(path: ""), outputType: outputType)
51+
existingOutput(inputFile: Self.singleInputKey, outputType: outputType)
5052
}
5153

5254
public func resolveRelativePaths(relativeTo absPath: AbsolutePath) -> OutputFileMap {
5355
let resolvedKeyValues: [(VirtualPath, [FileType : VirtualPath])] = entries.map {
5456
let resolvedKey: VirtualPath
5557
// Special case for single dependency record, leave it as is
56-
if $0.key == .relative(.init("")) {
58+
if $0.key == Self.singleInputKey {
5759
resolvedKey = $0.key
5860
} else {
5961
resolvedKey = $0.key.resolvedRelativePath(base: absPath)
@@ -78,7 +80,7 @@ public struct OutputFileMap: Equatable {
7880
let contents = try fileSystem.readFileContents(file)
7981
let result = try JSONDecoder().decode(OutputFileMapJSON.self, from: Data(contents.contents))
8082

81-
// Convert the loaded entries into virual output file map.
83+
// Convert the loaded entries into virtual output file map.
8284
var outputFileMap = OutputFileMap()
8385
outputFileMap.entries = try result.toVirtualOutputFileMap()
8486

Sources/SwiftDriver/Execution/ArgsResolver.swift

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import TSCBasic
14+
@_implementationOnly import Yams
1415

1516
/// Resolver for a job's argument template.
16-
public struct ArgsResolver {
17+
public final class ArgsResolver {
1718
/// The map of virtual path to the actual path.
18-
public var pathMapping: [VirtualPath: AbsolutePath]
19+
public var pathMapping: [VirtualPath: String]
1920

2021
/// The file system used by the resolver.
2122
private let fileSystem: FileSystem
@@ -24,13 +25,16 @@ public struct ArgsResolver {
2425
// FIXME: We probably need a dedicated type for this...
2526
private let temporaryDirectory: VirtualPath
2627

28+
private let lock = Lock()
29+
2730
public init(fileSystem: FileSystem, temporaryDirectory: VirtualPath? = nil) throws {
2831
self.pathMapping = [:]
2932
self.fileSystem = fileSystem
3033

3134
if let temporaryDirectory = temporaryDirectory {
3235
self.temporaryDirectory = temporaryDirectory
3336
} else {
37+
// FIXME: withTemporaryDirectory uses FileManager.default, need to create a FileSystem.temporaryDirectory api.
3438
let tmpDir: AbsolutePath = try withTemporaryDirectory(removeTreeOnDeinit: false) { path in
3539
// FIXME: TSC removes empty directories even when removeTreeOnDeinit is false. This seems like a bug.
3640
try fileSystem.writeFileContents(path.appending(component: ".keep-directory")) { $0 <<< "" }
@@ -60,22 +64,76 @@ public struct ArgsResolver {
6064
return flag
6165

6266
case .path(let path):
63-
// Return the path from the temporary directory if this is a temporary file.
64-
if path.isTemporary {
65-
let actualPath = temporaryDirectory.appending(component: path.name)
66-
return actualPath.name
67+
return try lock.withLock {
68+
return try unsafeResolve(path: path)
6769
}
70+
}
71+
}
72+
73+
/// Needs to be done inside of `lock`. Marked unsafe to make that more obvious.
74+
private func unsafeResolve(path: VirtualPath) throws -> String {
75+
// If there was a path mapping, use it.
76+
if let actualPath = pathMapping[path] {
77+
return actualPath
78+
}
79+
80+
// Return the path from the temporary directory if this is a temporary file.
81+
if path.isTemporary {
82+
let actualPath = temporaryDirectory.appending(component: path.name)
83+
if case let .fileList(_, fileList) = path {
84+
switch fileList {
85+
case let .list(items):
86+
try createFileList(path: actualPath, contents: items)
87+
case let .outputFileMap(map):
88+
try createFileList(path: actualPath, outputFileMap: map)
89+
}
90+
}
91+
let result = actualPath.name
92+
pathMapping[path] = result
93+
return result
94+
}
6895

69-
// If there was a path mapping, use it.
70-
if let actualPath = pathMapping[path] {
71-
return actualPath.pathString
96+
// Otherwise, return the path.
97+
let result = path.name
98+
pathMapping[path] = result
99+
return result
100+
}
101+
102+
private func createFileList(path: VirtualPath, contents: [VirtualPath]) throws {
103+
// FIXME: Need a way to support this for distributed build systems...
104+
if let absPath = path.absolutePath {
105+
try fileSystem.writeFileContents(absPath) { out in
106+
for path in contents {
107+
try! out <<< unsafeResolve(path: path) <<< "\n"
108+
}
72109
}
110+
}
111+
}
73112

74-
// Otherwise, return the path.
75-
return path.name
113+
private func createFileList(path: VirtualPath, outputFileMap: OutputFileMap) throws {
114+
// FIXME: Need a way to support this for distributed build systems...
115+
if let absPath = path.absolutePath {
116+
// This uses Yams to escape and quote strings, but not to output the whole yaml file because
117+
// it sometimes outputs mappings in explicit block format (https://yaml.org/spec/1.2/spec.html#id2798057)
118+
// and the frontend (llvm) only seems to support implicit block format.
119+
try fileSystem.writeFileContents(absPath) { out in
120+
for (input, map) in outputFileMap.entries {
121+
out <<< quoteAndEscape(path: input) <<< ":\n"
122+
for (type, output) in map {
123+
out <<< " " <<< type.name <<< ": " <<< quoteAndEscape(path: output) <<< "\n"
124+
}
125+
}
126+
}
76127
}
77128
}
78129

130+
private func quoteAndEscape(path: VirtualPath) -> String {
131+
let inputNode = Node.scalar(Node.Scalar(try! unsafeResolve(path: path), Tag(.str), .doubleQuoted))
132+
let string = try! Yams.serialize(node: inputNode)
133+
// Remove the newline from the end
134+
return string.trimmingCharacters(in: .whitespacesAndNewlines)
135+
}
136+
79137
private func createResponseFileIfNeeded(for job: Job, resolvedArguments: inout [String], forceResponseFiles: Bool) throws -> Bool {
80138
if forceResponseFiles ||
81139
(job.supportsResponseFiles && !commandLineFitsWithinSystemLimits(path: resolvedArguments[0], args: resolvedArguments)) {

Sources/SwiftDriver/Execution/DriverExecutor.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import TSCBasic
1414
import Foundation
1515

1616
public protocol DriverExecutor {
17+
var resolver: ArgsResolver { get }
18+
1719
/// Execute a single job and capture the output.
1820
@discardableResult
1921
func execute(job: Job,
@@ -82,7 +84,7 @@ public final class SwiftDriverExecutor: DriverExecutor {
8284
let diagnosticsEngine: DiagnosticsEngine
8385
let processSet: ProcessSet
8486
let fileSystem: FileSystem
85-
let resolver: ArgsResolver
87+
public let resolver: ArgsResolver
8688
let env: [String: String]
8789

8890
public init(diagnosticsEngine: DiagnosticsEngine,
@@ -109,13 +111,13 @@ public final class SwiftDriverExecutor: DriverExecutor {
109111
for (envVar, value) in job.extraEnvironment {
110112
try ProcessEnv.setVar(envVar, value: value)
111113
}
112-
114+
113115
try exec(path: arguments[0], args: arguments)
114116
fatalError("unreachable, exec always throws on failure")
115117
} else {
116118
var childEnv = env
117119
childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new })
118-
120+
119121
let process = try Process.launchProcess(arguments: arguments, env: childEnv)
120122
return try process.waitUntilExit()
121123
}

0 commit comments

Comments
 (0)