Skip to content

Commit e9b27e8

Browse files
authored
Merge pull request #144 from cltnschlosser/cs_fileLists
File list support
2 parents 8f6fe1b + 48e5ff9 commit e9b27e8

20 files changed

+622
-146
lines changed

Sources/SwiftDriver/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ add_library(SwiftDriver
6767
Utilities/DOTJobGraphSerializer.swift
6868
Utilities/DateAdditions.swift
6969
Utilities/Diagnostics.swift
70+
Utilities/FileList.swift
7071
Utilities/FileType.swift
7172
Utilities/PredictableRandomNumberGenerator.swift
7273
Utilities/RelativePathAdditions.swift

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public struct Driver {
2020
case invalidDriverName(String)
2121
case invalidInput(String)
2222
case noInputFiles
23+
case invalidArgumentValue(String, String)
2324
case relativeFrontendPath(String)
2425
case subcommandPassedToDriver
2526
case integratedReplRemoved
@@ -45,6 +46,8 @@ public struct Driver {
4546
return "invalid input: \(input)"
4647
case .noInputFiles:
4748
return "no input files"
49+
case .invalidArgumentValue(let option, let value):
50+
return "invalid value '\(value)' in '\(option)'"
4851
case .relativeFrontendPath(let path):
4952
// TODO: where is this error thrown
5053
return "relative frontend path: \(path)"
@@ -133,6 +136,15 @@ public struct Driver {
133136
/// The mapping from input files to output files for each kind.
134137
internal let outputFileMap: OutputFileMap?
135138

139+
/// The number of files required before making a file list.
140+
internal let fileListThreshold: Int
141+
142+
/// Should use file lists for inputs (number of inputs exceeds `fileListThreshold`).
143+
public let shouldUseInputFileList: Bool
144+
145+
/// VirtualPath for shared all sources file list. `nil` if unused.
146+
public let allSourcesFileList: VirtualPath?
147+
136148
/// The mode in which the compiler will execute.
137149
public let compilerMode: CompilerMode
138150

@@ -272,7 +284,6 @@ public struct Driver {
272284
throw Error.subcommandPassedToDriver
273285
}
274286

275-
276287
var args = try Self.expandResponseFiles(args, fileSystem: fileSystem, diagnosticsEngine: self.diagnosticEngine)
277288

278289
self.driverKind = try Self.determineDriverKind(args: &args)
@@ -329,6 +340,16 @@ public struct Driver {
329340
self.outputFileMap = outputFileMap
330341
}
331342

343+
self.fileListThreshold = try Self.computeFileListThreshold(&self.parsedOptions, diagnosticsEngine: diagnosticsEngine)
344+
self.shouldUseInputFileList = inputFiles.count > fileListThreshold
345+
if shouldUseInputFileList {
346+
let swiftInputs = inputFiles.filter(\.type.isPartOfSwiftCompilation)
347+
let path = RelativePath(createTemporaryFileName(prefix: "sources"))
348+
self.allSourcesFileList = .fileList(path, .list(swiftInputs.map(\.file)))
349+
} else {
350+
self.allSourcesFileList = nil
351+
}
352+
332353
// Figure out the primary outputs from the driver.
333354
(self.compilerOutputType, self.linkerOutputType) = Self.determinePrimaryOutputs(&parsedOptions, driverKind: driverKind, diagnosticsEngine: diagnosticEngine)
334355

@@ -823,6 +844,37 @@ extension Driver {
823844
}
824845
}
825846

847+
extension Driver {
848+
private static func computeFileListThreshold(
849+
_ parsedOptions: inout ParsedOptions,
850+
diagnosticsEngine: DiagnosticsEngine
851+
) throws -> Int {
852+
let hasUseFileLists = parsedOptions.hasArgument(.driverUseFilelists)
853+
854+
if hasUseFileLists {
855+
diagnosticsEngine.emit(.warn_use_filelists_deprecated)
856+
}
857+
858+
if let threshold = parsedOptions.getLastArgument(.driverFilelistThreshold)?.asSingle {
859+
if let thresholdInt = Int(threshold) {
860+
return thresholdInt
861+
} else {
862+
throw Error.invalidArgumentValue(Option.driverFilelistThreshold.spelling, threshold)
863+
}
864+
} else if hasUseFileLists {
865+
return 0
866+
}
867+
868+
return 128
869+
}
870+
}
871+
872+
private extension Diagnostic.Message {
873+
static var warn_use_filelists_deprecated: Diagnostic.Message {
874+
.warning("the option '-driver-use-filelists' is deprecated; use '-driver-filelist-threshold=0' instead")
875+
}
876+
}
877+
826878
extension Driver {
827879
/// Compute the compiler mode based on the options.
828880
private static func computeCompilerMode(
@@ -1902,15 +1954,15 @@ extension Driver {
19021954
_ = parsedOptions.hasArgument(isOutput)
19031955
}
19041956

1905-
var parentPath: VirtualPath
1957+
let parentPath: VirtualPath
19061958
if let projectDirectory = projectDirectory {
19071959
// If the build system has created a Project dir for us to include the file, use it.
19081960
parentPath = projectDirectory
19091961
} else {
19101962
parentPath = moduleOutputPath.parentDirectory
19111963
}
19121964

1913-
return try parentPath.appending(component: moduleName).replacingExtension(with: type)
1965+
return parentPath.appending(component: moduleName).replacingExtension(with: type)
19141966
}
19151967

19161968
// 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: 72 additions & 14 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,26 +64,80 @@ 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-
}
68-
69-
// If there was a path mapping, use it.
70-
if let actualPath = pathMapping[path] {
71-
return actualPath.pathString
67+
return try lock.withLock {
68+
return try unsafeResolve(path: path)
7269
}
73-
74-
// Otherwise, return the path.
75-
return path.name
7670
case .responseFilePath(let path):
7771
return "@\(try resolve(.path(path)))"
7872
case let .joinedOptionAndPath(option, path):
7973
return option + (try resolve(.path(path)))
8074
}
8175
}
8276

77+
/// Needs to be done inside of `lock`. Marked unsafe to make that more obvious.
78+
private func unsafeResolve(path: VirtualPath) throws -> String {
79+
// If there was a path mapping, use it.
80+
if let actualPath = pathMapping[path] {
81+
return actualPath
82+
}
83+
84+
// Return the path from the temporary directory if this is a temporary file.
85+
if path.isTemporary {
86+
let actualPath = temporaryDirectory.appending(component: path.name)
87+
if case let .fileList(_, fileList) = path {
88+
switch fileList {
89+
case let .list(items):
90+
try createFileList(path: actualPath, contents: items)
91+
case let .outputFileMap(map):
92+
try createFileList(path: actualPath, outputFileMap: map)
93+
}
94+
}
95+
let result = actualPath.name
96+
pathMapping[path] = result
97+
return result
98+
}
99+
100+
// Otherwise, return the path.
101+
let result = path.name
102+
pathMapping[path] = result
103+
return result
104+
}
105+
106+
private func createFileList(path: VirtualPath, contents: [VirtualPath]) throws {
107+
// FIXME: Need a way to support this for distributed build systems...
108+
if let absPath = path.absolutePath {
109+
try fileSystem.writeFileContents(absPath) { out in
110+
for path in contents {
111+
try! out <<< unsafeResolve(path: path) <<< "\n"
112+
}
113+
}
114+
}
115+
}
116+
117+
private func createFileList(path: VirtualPath, outputFileMap: OutputFileMap) throws {
118+
// FIXME: Need a way to support this for distributed build systems...
119+
if let absPath = path.absolutePath {
120+
// This uses Yams to escape and quote strings, but not to output the whole yaml file because
121+
// it sometimes outputs mappings in explicit block format (https://yaml.org/spec/1.2/spec.html#id2798057)
122+
// and the frontend (llvm) only seems to support implicit block format.
123+
try fileSystem.writeFileContents(absPath) { out in
124+
for (input, map) in outputFileMap.entries {
125+
out <<< quoteAndEscape(path: input) <<< ":\n"
126+
for (type, output) in map {
127+
out <<< " " <<< type.name <<< ": " <<< quoteAndEscape(path: output) <<< "\n"
128+
}
129+
}
130+
}
131+
}
132+
}
133+
134+
private func quoteAndEscape(path: VirtualPath) -> String {
135+
let inputNode = Node.scalar(Node.Scalar(try! unsafeResolve(path: path), Tag(.str), .doubleQuoted))
136+
let string = try! Yams.serialize(node: inputNode)
137+
// Remove the newline from the end
138+
return string.trimmingCharacters(in: .whitespacesAndNewlines)
139+
}
140+
83141
private func createResponseFileIfNeeded(for job: Job, resolvedArguments: inout [String], forceResponseFiles: Bool) throws -> Bool {
84142
if forceResponseFiles ||
85143
(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,
@@ -87,7 +89,7 @@ public final class SwiftDriverExecutor: DriverExecutor {
8789
let diagnosticsEngine: DiagnosticsEngine
8890
let processSet: ProcessSet
8991
let fileSystem: FileSystem
90-
let resolver: ArgsResolver
92+
public let resolver: ArgsResolver
9193
let env: [String: String]
9294

9395
public init(diagnosticsEngine: DiagnosticsEngine,
@@ -114,13 +116,13 @@ public final class SwiftDriverExecutor: DriverExecutor {
114116
for (envVar, value) in job.extraEnvironment {
115117
try ProcessEnv.setVar(envVar, value: value)
116118
}
117-
119+
118120
try exec(path: arguments[0], args: arguments)
119121
fatalError("unreachable, exec always throws on failure")
120122
} else {
121123
var childEnv = env
122124
childEnv.merge(job.extraEnvironment, uniquingKeysWith: { (_, new) in new })
123-
125+
124126
let process = try Process.launchProcess(arguments: arguments, env: childEnv)
125127
return try process.waitUntilExit()
126128
}

0 commit comments

Comments
 (0)