Skip to content

Commit 59d018b

Browse files
authored
Merge pull request #13 from apple/master
merge upstream
2 parents 398c57b + c835d02 commit 59d018b

24 files changed

+491
-203
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212
import TSCBasic
1313
import TSCUtility
14+
import Foundation
1415

1516
/// How should the Swift module output be handled?
1617
public enum ModuleOutput: Equatable {
@@ -46,7 +47,7 @@ public struct Driver {
4647
case subcommandPassedToDriver
4748
case relativeFrontendPath(String)
4849
}
49-
50+
5051
/// The set of environment variables that are visible to the driver and
5152
/// processes it launches. This is a hook for testing; in actual use
5253
/// it should be identical to the real environment.
@@ -79,6 +80,9 @@ public struct Driver {
7980
/// The set of input files
8081
public let inputFiles: [TypedVirtualPath]
8182

83+
/// The last time each input file was modified, recorded at the start of the build.
84+
public let recordedInputModificationDates: [TypedVirtualPath: Date]
85+
8286
/// The mapping from input files to output files for each kind.
8387
internal let outputFileMap: OutputFileMap?
8488

@@ -246,14 +250,28 @@ public struct Driver {
246250
// Classify and collect all of the input files.
247251
let inputFiles = try Self.collectInputFiles(&self.parsedOptions)
248252
self.inputFiles = inputFiles
253+
self.recordedInputModificationDates = .init(uniqueKeysWithValues:
254+
Set(inputFiles).compactMap {
255+
if case .absolute(let absolutePath) = $0.file,
256+
let modTime = try? localFileSystem.getFileInfo(absolutePath).modTime {
257+
return ($0, modTime)
258+
}
259+
return nil
260+
})
249261

262+
let outputFileMap: OutputFileMap?
250263
// Initialize an empty output file map, which will be populated when we start creating jobs.
251264
if let outputFileMapArg = parsedOptions.getLastArgument(.outputFileMap)?.asSingle {
252265
let path = try AbsolutePath(validating: outputFileMapArg)
253-
self.outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
266+
outputFileMap = try .load(file: path, diagnosticEngine: diagnosticEngine)
267+
} else {
268+
outputFileMap = nil
254269
}
255-
else {
256-
self.outputFileMap = nil
270+
271+
if let workingDirectory = self.workingDirectory {
272+
self.outputFileMap = outputFileMap?.resolveRelativePaths(relativeTo: workingDirectory)
273+
} else {
274+
self.outputFileMap = outputFileMap
257275
}
258276

259277
// Determine the compilation mode.
@@ -441,7 +459,7 @@ extension Driver {
441459
var isEscaping = false
442460
// Indicates if we are currently parsing quoted text.
443461
var quoted = false
444-
462+
445463
for char in line {
446464
// Backslash escapes to the next character.
447465
if char == #"\"#, !isEscaping {
@@ -480,7 +498,7 @@ extension Driver {
480498
return content.split { $0 == "\n" || $0 == "\r\n" }
481499
.flatMap { tokenizeResponseFileLine($0) }
482500
}
483-
501+
484502
/// Recursively expands the response files.
485503
/// - Parameter visitedResponseFiles: Set containing visited response files to detect recursive parsing.
486504
private static func expandResponseFiles(
@@ -605,7 +623,7 @@ extension Driver {
605623
return
606624
}
607625

608-
if jobs.contains(where: { $0.requiresInPlaceExecution }) {
626+
if jobs.contains(where: { $0.requiresInPlaceExecution }) || jobs.count == 1 {
609627
assert(jobs.count == 1, "Cannot execute in place for multi-job build plans")
610628
return try executeJobInPlace(jobs[0], resolver: resolver, forceResponseFiles: forceResponseFiles)
611629
}
@@ -619,7 +637,8 @@ extension Driver {
619637
executorDelegate: executorDelegate,
620638
numParallelJobs: numParallelJobs,
621639
processSet: processSet,
622-
forceResponseFiles: forceResponseFiles
640+
forceResponseFiles: forceResponseFiles,
641+
recordedInputModificationDates: recordedInputModificationDates
623642
)
624643
try jobExecutor.execute(env: env)
625644
}
@@ -645,6 +664,8 @@ extension Driver {
645664
try ProcessEnv.setVar(envVar, value: value)
646665
}
647666

667+
try job.verifyInputsNotModified(since: self.recordedInputModificationDates)
668+
648669
return try exec(path: arguments[0], args: arguments)
649670
}
650671

@@ -1148,12 +1169,12 @@ extension Driver {
11481169
private static func baseNameWithoutExtension(_ path: String, hasExtension: inout Bool) -> String {
11491170
if let absolute = try? AbsolutePath(validating: path) {
11501171
hasExtension = absolute.extension != nil
1151-
return absolute.basenameWithoutAllExts
1172+
return absolute.basenameWithoutExt
11521173
}
11531174

11541175
if let relative = try? RelativePath(validating: path) {
11551176
hasExtension = relative.extension != nil
1156-
return relative.basenameWithoutAllExts
1177+
return relative.basenameWithoutExt
11571178
}
11581179

11591180
hasExtension = false
@@ -1251,7 +1272,7 @@ extension Driver {
12511272
// This value will fail the isSwiftIdentifier test below.
12521273
moduleName = ""
12531274
}
1254-
1275+
12551276
func fallbackOrDiagnose(_ error: Diagnostic.Message) {
12561277
// FIXME: Current driver notes that this is a "fallback module name".
12571278
if compilerOutputType == nil || maybeBuildingExecutable(&parsedOptions, linkerOutputType: linkerOutputType) {
@@ -1330,7 +1351,7 @@ extension Driver {
13301351
sdkPath = try? toolchain.defaultSDKPath()?.pathString
13311352
}
13321353
}
1333-
1354+
13341355
// An empty string explicitly clears the SDK.
13351356
if sdkPath == "" {
13361357
sdkPath = nil
@@ -1445,7 +1466,7 @@ extension Driver {
14451466
#else
14461467
static let defaultToolchainType: Toolchain.Type = GenericUnixToolchain.self
14471468
#endif
1448-
1469+
14491470
static func computeToolchain(
14501471
_ explicitTarget: Triple?,
14511472
diagnosticsEngine: DiagnosticsEngine,

Sources/SwiftDriver/Driver/DriverKind.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extension DriverKind {
7070
return "Swift Module Wrapper"
7171
}
7272
}
73-
73+
7474
public var seeAlsoHelpMessage: String? {
7575
switch self {
7676
case .interactive:

Sources/SwiftDriver/Driver/OutputFileMap.swift

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,25 @@ public struct OutputFileMap: Equatable {
4747
try! existingOutput(inputFile: VirtualPath(path: ""), outputType: outputType)
4848
}
4949

50+
public func resolveRelativePaths(relativeTo absPath: AbsolutePath) -> OutputFileMap {
51+
let resolvedKeyValues: [(VirtualPath, [FileType : VirtualPath])] = entries.map {
52+
let resolvedKey: VirtualPath
53+
// Special case for single dependency record, leave it as is
54+
if ($0.key == .relative(.init(""))) {
55+
resolvedKey = $0.key
56+
} else {
57+
resolvedKey = $0.key.resolvedRelativePath(base: absPath)
58+
}
59+
let resolvedValue = $0.value.mapValues {
60+
$0.resolvedRelativePath(base: absPath)
61+
}
62+
return (resolvedKey, resolvedValue)
63+
}
64+
return OutputFileMap(entries: .init(resolvedKeyValues, uniquingKeysWith: { _,_ in
65+
fatalError("Paths collided after resolving")
66+
}))
67+
}
68+
5069
/// Load the output file map at the given path.
5170
public static func load(
5271
file: AbsolutePath,
@@ -156,9 +175,9 @@ fileprivate struct OutputFileMapJSON: Codable {
156175

157176
/// Converts into virtual path entries.
158177
func toVirtualOutputFileMap() throws -> [VirtualPath : [FileType : VirtualPath]] {
159-
Dictionary(uniqueKeysWithValues: try entries.map { input, entry in
178+
Dictionary(try entries.map { input, entry in
160179
(try VirtualPath(path: input), try entry.paths.mapValues(VirtualPath.init(path:)))
161-
})
180+
}, uniquingKeysWith: { $1 })
162181
}
163182

164183
/// Converts from virtual path entries
@@ -186,3 +205,10 @@ extension String {
186205
return self + "." + ext
187206
}
188207
}
208+
209+
extension VirtualPath {
210+
fileprivate func resolvedRelativePath(base: AbsolutePath) -> VirtualPath {
211+
guard case let .relative(relPath) = self else { return self }
212+
return .absolute(.init(base, relPath))
213+
}
214+
}

Sources/SwiftDriver/Execution/JobExecutor.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public final class JobExecutor {
119119

120120
/// The resolver for argument template.
121121
let argsResolver: ArgsResolver
122-
122+
123123
/// The environment variables.
124124
let env: [String: String]
125125

@@ -138,14 +138,18 @@ public final class JobExecutor {
138138
/// If true, always use response files to pass command line arguments.
139139
let forceResponseFiles: Bool
140140

141+
/// The last time each input file was modified, recorded at the start of the build.
142+
public let recordedInputModificationDates: [TypedVirtualPath: Date]
143+
141144
init(
142145
argsResolver: ArgsResolver,
143146
env: [String: String],
144147
producerMap: [VirtualPath: Job],
145148
executorDelegate: JobExecutorDelegate,
146149
jobQueue: OperationQueue,
147150
processSet: ProcessSet?,
148-
forceResponseFiles: Bool
151+
forceResponseFiles: Bool,
152+
recordedInputModificationDates: [TypedVirtualPath: Date]
149153
) {
150154
self.producerMap = producerMap
151155
self.argsResolver = argsResolver
@@ -154,6 +158,7 @@ public final class JobExecutor {
154158
self.jobQueue = jobQueue
155159
self.processSet = processSet
156160
self.forceResponseFiles = forceResponseFiles
161+
self.recordedInputModificationDates = recordedInputModificationDates
157162
}
158163
}
159164

@@ -175,20 +180,25 @@ public final class JobExecutor {
175180
/// If true, always use response files to pass command line arguments.
176181
let forceResponseFiles: Bool
177182

183+
/// The last time each input file was modified, recorded at the start of the build.
184+
public let recordedInputModificationDates: [TypedVirtualPath: Date]
185+
178186
public init(
179187
jobs: [Job],
180188
resolver: ArgsResolver,
181189
executorDelegate: JobExecutorDelegate,
182190
numParallelJobs: Int? = nil,
183191
processSet: ProcessSet? = nil,
184-
forceResponseFiles: Bool = false
192+
forceResponseFiles: Bool = false,
193+
recordedInputModificationDates: [TypedVirtualPath: Date] = [:]
185194
) {
186195
self.jobs = jobs
187196
self.argsResolver = resolver
188197
self.executorDelegate = executorDelegate
189198
self.numParallelJobs = numParallelJobs ?? 1
190199
self.processSet = processSet
191200
self.forceResponseFiles = forceResponseFiles
201+
self.recordedInputModificationDates = recordedInputModificationDates
192202
}
193203

194204
/// Execute all jobs.
@@ -227,7 +237,8 @@ public final class JobExecutor {
227237
executorDelegate: executorDelegate,
228238
jobQueue: jobQueue,
229239
processSet: processSet,
230-
forceResponseFiles: forceResponseFiles
240+
forceResponseFiles: forceResponseFiles,
241+
recordedInputModificationDates: recordedInputModificationDates
231242
)
232243
}
233244
}
@@ -382,6 +393,8 @@ class ExecuteJobRule: LLBuildRule {
382393
let arguments: [String] = try resolver.resolveArgumentList(for: job,
383394
forceResponseFiles: context.forceResponseFiles)
384395

396+
try job.verifyInputsNotModified(since: context.recordedInputModificationDates)
397+
385398
let process = try context.executorDelegate.launchProcess(
386399
for: job, arguments: arguments, env: env
387400
)

Sources/SwiftDriver/Jobs/CompileJob.swift

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,57 @@ extension Driver {
2525
}
2626
}
2727

28-
/// Add the compiler inputs for a frontend compilation job, and return the corresponding primary set of outputs.
29-
func addCompileInputs(primaryInputs: [TypedVirtualPath], inputs: inout [TypedVirtualPath], commandLine: inout [Job.ArgTemplate]) -> [TypedVirtualPath] {
28+
fileprivate mutating func computePrimaryOutput(for input: TypedVirtualPath, outputType: FileType,
29+
isTopLevel: Bool) -> TypedVirtualPath {
30+
if let path = outputFileMap?.existingOutput(inputFile: input.file, outputType: outputType) {
31+
return TypedVirtualPath(file: path, type: outputType)
32+
}
33+
34+
if isTopLevel {
35+
if let baseOutput = parsedOptions.getLastArgument(.o)?.asSingle,
36+
let baseOutputPath = try? VirtualPath(path: baseOutput){
37+
return TypedVirtualPath(file: baseOutputPath, type: outputType)
38+
} else if compilerOutputType?.isTextual == true {
39+
return TypedVirtualPath(file: .standardOutput, type: outputType)
40+
}
41+
}
42+
43+
let baseName: String
44+
if (!compilerMode.usesPrimaryFileInputs && numThreads == 0) {
45+
baseName = moduleName
46+
} else {
47+
baseName = input.file.basenameWithoutExt
48+
}
49+
50+
if !isTopLevel {
51+
return TypedVirtualPath(file:VirtualPath.temporary(.init(baseName.appendingFileTypeExtension(outputType))),
52+
type: outputType)
53+
}
54+
55+
return TypedVirtualPath(file: .relative(.init(baseName.appendingFileTypeExtension(outputType))), type: outputType)
56+
}
57+
58+
/// Add the compiler inputs for a frontend compilation job, and return the
59+
/// corresponding primary set of outputs.
60+
mutating func addCompileInputs(primaryInputs: [TypedVirtualPath],
61+
inputs: inout [TypedVirtualPath],
62+
commandLine: inout [Job.ArgTemplate]) -> [TypedVirtualPath] {
63+
// Is this compile job top-level
64+
let isTopLevel: Bool
65+
66+
switch compilerOutputType {
67+
case .assembly, .sil, .raw_sil, .llvmIR, .ast:
68+
isTopLevel = true
69+
case .object:
70+
isTopLevel = (linkerOutputType == nil)
71+
case .swift, .sib, .image, .dSYM, .dependencies, .autolink,
72+
.swiftModule, .swiftDocumentation, .swiftInterface,
73+
.swiftSourceInfoFile, .raw_sib, .llvmBitcode, .diagnostics,
74+
.objcHeader, .swiftDeps, .remap, .importedModules, .tbd, .moduleTrace,
75+
.indexData, .optimizationRecord, .pcm, .pch, nil:
76+
isTopLevel = false
77+
}
78+
3079
// Collect the set of input files that are part of the Swift compilation.
3180
let swiftInputFiles: [TypedVirtualPath] = inputFiles.compactMap { inputFile in
3281
if inputFile.type.isPartOfSwiftCompilation {
@@ -58,21 +107,19 @@ extension Driver {
58107
// add an output for the input.
59108
if isPrimary || numThreads > 0,
60109
let compilerOutputType = compilerOutputType {
61-
let output = (outputFileMap ?? OutputFileMap()).getOutput(
62-
inputFile: input.file,
63-
outputType: compilerOutputType
64-
)
65-
primaryOutputs.append(TypedVirtualPath(file: output, type: compilerOutputType))
110+
primaryOutputs.append(computePrimaryOutput(for: input,
111+
outputType: compilerOutputType,
112+
isTopLevel: isTopLevel))
66113
}
67114
}
68115

69116
// When not using primary file inputs or multithreading, add a single output.
70117
if !usesPrimaryFileInputs && numThreads == 0,
71118
let outputType = compilerOutputType {
72-
let existingOutputPath = outputFileMap?.existingOutputForSingleInput(
73-
outputType: outputType)
74-
let output = existingOutputPath ?? VirtualPath.temporary(.init(moduleName.appendingFileTypeExtension(outputType)))
75-
primaryOutputs.append(TypedVirtualPath(file: output, type: outputType))
119+
primaryOutputs.append(computePrimaryOutput(
120+
for: TypedVirtualPath(file: try! VirtualPath(path: ""),
121+
type: swiftInputFiles[0].type),
122+
outputType: outputType, isTopLevel: isTopLevel))
76123
}
77124

78125
return primaryOutputs

0 commit comments

Comments
 (0)