Skip to content

Commit 0606137

Browse files
author
David Ungar
authored
Merge pull request swiftlang#583 from davidungar/post-compile-remarks
[Incremental] Add remarks explaining post-compile builds
2 parents 9b87e86 + c917563 commit 0606137

File tree

3 files changed

+83
-17
lines changed

3 files changed

+83
-17
lines changed

Sources/SwiftDriver/IncrementalCompilation/IncrementalCompilationState.swift

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public final class IncrementalCompilationState {
3737
@_spi(Testing) public let moduleDependencyGraph: ModuleDependencyGraph
3838

3939
/// If non-null outputs information for `-driver-show-incremental` for input path
40-
private let reporter: Reporter?
40+
public let reporter: Reporter?
4141

4242
/// All of the pre-compile or compilation job (groups) known to be required (i.e. in 1st wave).
4343
/// Already batched, and in order of input files.
@@ -307,10 +307,62 @@ extension IncrementalCompilationState {
307307
}
308308
// MARK: - Scheduling post-compile jobs
309309
extension IncrementalCompilationState {
310-
public func canSkipPostCompile(job: Job) -> Bool {
311-
job.outputs.allSatisfy {output in
312-
let fileModTime = (try? fileSystem.lastModificationTime(for: output.file)) ?? .distantFuture
313-
return fileModTime <= buildEndTime}
310+
/// Only used when no compilations have run; otherwise the caller assumes every post-compile
311+
/// job is needed, and saves the cost of the filesystem accesses by not calling this function.
312+
/// (For instance, if a build is cancelled in the merge-module phase, the compilations may be up-to-date
313+
/// but the postcompile-jobs (e.g. link-edit) may still need to be run.
314+
/// Since the use-case is rare, this function can afford to be expensive.
315+
/// Unlike the check in `IncrementalStateComputer.computeChangedInputs`,
316+
/// this function does not rely on build record information, which makes it more expensive but more robust.
317+
public func canSkip(postCompileJob: Job) -> Bool {
318+
func report(skipping: Bool, _ details: String, _ file: TypedVirtualPath? = nil) {
319+
reporter?.report(
320+
"\(skipping ? "S" : "Not s")kipping job: \(postCompileJob.descriptionForLifecycle); \(details)",
321+
file)
322+
}
323+
324+
guard let (oldestOutput, oldestOutputModTime) =
325+
findOldestOutputForSkipping(postCompileJob: postCompileJob)
326+
else {
327+
report(skipping: false, "No outputs")
328+
return false
329+
}
330+
guard .distantPast < oldestOutputModTime
331+
else {
332+
report(skipping: false, "Missing output", oldestOutput)
333+
return false
334+
}
335+
if let newerInput = findAnInputOf(postCompileJob: postCompileJob,
336+
newerThan: oldestOutputModTime) {
337+
report(skipping: false, "Input \(newerInput.file.basename) is newer than output", oldestOutput)
338+
return false
339+
}
340+
report(skipping: true, "oldest output is current", oldestOutput)
341+
return true
342+
}
343+
344+
private func findOldestOutputForSkipping(postCompileJob: Job) -> (TypedVirtualPath, Date)? {
345+
var oldestOutputAndModTime: (TypedVirtualPath, Date)? = nil
346+
for output in postCompileJob.outputs {
347+
guard let outputModTime = modTime(output)
348+
else {
349+
oldestOutputAndModTime = (output, .distantPast)
350+
break
351+
}
352+
oldestOutputAndModTime = oldestOutputAndModTime.map {
353+
$0.1 < outputModTime ? $0 : (output, outputModTime)
354+
}
355+
?? (output, outputModTime)
356+
}
357+
return oldestOutputAndModTime
358+
}
359+
private func findAnInputOf( postCompileJob: Job, newerThan outputModTime: Date) -> TypedVirtualPath? {
360+
postCompileJob.inputs.first { input in
361+
outputModTime < (modTime(input) ?? .distantFuture)
362+
}
363+
}
364+
private func modTime(_ path: TypedVirtualPath) -> Date? {
365+
try? fileSystem.lastModificationTime(for: path.file)
314366
}
315367
}
316368

@@ -356,7 +408,7 @@ extension IncrementalCompilationState {
356408
/// - message: The message to emit in the remark.
357409
/// - path: If non-nil, the path of some file. If the output for an incremental job, will print out the
358410
/// source and object files.
359-
func report(_ message: String, _ pathIfGiven: TypedVirtualPath?) {
411+
public func report(_ message: String, _ pathIfGiven: TypedVirtualPath?) {
360412
guard let path = pathIfGiven,
361413
let outputFileMap = outputFileMap,
362414
let input = path.type == .swift ? path.file : outputFileMap.getInput(outputFile: path.file)
@@ -370,7 +422,7 @@ extension IncrementalCompilationState {
370422
}
371423

372424
/// Entry point for a simple path, won't print the compile job, path could be anything.
373-
func report(_ message: String, _ path: VirtualPath?) {
425+
public func report(_ message: String, _ path: VirtualPath?) {
374426
guard let path = path
375427
else {
376428
report(message)
@@ -381,7 +433,7 @@ extension IncrementalCompilationState {
381433
}
382434

383435
/// Entry point if no path.
384-
func report(_ message: String) {
436+
public func report(_ message: String) {
385437
diagnosticEngine.emit(.remark_incremental_compilation(because: message))
386438
}
387439

Sources/SwiftDriverExecution/MultiJobExecutor.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -401,18 +401,26 @@ class ExecuteAllJobsRule: LLBuildRule {
401401

402402
/// After all compilation jobs have run, figure which, for instance link, jobs must run
403403
private func schedulePostCompileJobs(_ engine: LLTaskBuildEngine) {
404-
for postCompileIndex in context.postCompileIndices {
405-
let job = context.jobs[postCompileIndex]
406-
/// If any compile jobs ran, skip the expensive mod-time checks
407-
if context.primaryIndices.isEmpty,
408-
let incrementalCompilationState = context.incrementalCompilationState,
409-
incrementalCompilationState.canSkipPostCompile(job: job) {
410-
continue
411-
}
404+
func schedule(_ postCompileIndex: Int) {
412405
engine.taskNeedsInput(ExecuteJobRule.RuleKey(index: postCompileIndex),
413406
inputID: postCompileIndex)
414407
}
415-
}
408+
let didAnyCompileJobsRun = !context.primaryIndices.isEmpty
409+
/// If any compile jobs ran, skip the expensive mod-time checks
410+
let scheduleEveryPostCompileJob = didAnyCompileJobsRun
411+
if let incrementalCompilationState = context.incrementalCompilationState,
412+
!scheduleEveryPostCompileJob {
413+
for postCompileIndex in context.postCompileIndices
414+
where !incrementalCompilationState.canSkip(postCompileJob: context.jobs[postCompileIndex]) {
415+
schedule(postCompileIndex)
416+
}
417+
}
418+
else {
419+
context.incrementalCompilationState?.reporter?.report(
420+
"Scheduling all post-compile jobs because something was compiled")
421+
context.postCompileIndices.forEach(schedule)
422+
}
423+
}
416424

417425
override func inputsAvailable(_ engine: LLTaskBuildEngine) {
418426
engine.taskIsComplete(DriverBuildValue.jobExecution(success: allInputsSucceeded))

Tests/SwiftDriverTests/IncrementalCompilationTests.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@ extension IncrementalCompilationTests {
646646
"Forming batch job from 2 constituents: main.swift, other.swift",
647647
"Starting Compiling main.swift, other.swift",
648648
"Finished Compiling main.swift, other.swift",
649+
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
649650
"Starting Linking theModule",
650651
"Finished Linking theModule",
651652
],
@@ -667,6 +668,7 @@ extension IncrementalCompilationTests {
667668
"Incremental compilation: Skipping input: {compile: other.o <= other.swift}",
668669
"Skipped Compiling main.swift",
669670
"Skipped Compiling other.swift",
671+
"Incremental compilation: Skipping job: Linking theModule; oldest output is current",
670672
],
671673
whenAutolinking: [])
672674
}
@@ -692,6 +694,7 @@ extension IncrementalCompilationTests {
692694
"Forming batch job from 1 constituents: other.swift",
693695
"Starting Compiling other.swift",
694696
"Finished Compiling other.swift",
697+
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
695698
"Starting Linking theModule",
696699
"Finished Linking theModule",
697700
"Skipped Compiling main.swift",
@@ -723,6 +726,7 @@ extension IncrementalCompilationTests {
723726
"Forming batch job from 2 constituents: main.swift, other.swift",
724727
"Starting Compiling main.swift, other.swift",
725728
"Finished Compiling main.swift, other.swift",
729+
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
726730
"Starting Linking theModule",
727731
"Finished Linking theModule",
728732
],
@@ -762,6 +766,7 @@ extension IncrementalCompilationTests {
762766
"Forming batch job from 1 constituents: other.swift",
763767
"Starting Compiling other.swift",
764768
"Finished Compiling other.swift",
769+
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
765770
"Starting Linking theModule",
766771
"Finished Linking theModule",
767772
],
@@ -793,6 +798,7 @@ extension IncrementalCompilationTests {
793798
"Adding {compile: main.swift} to batch 0",
794799
"Adding {compile: other.swift} to batch 0",
795800
"Forming batch job from 2 constituents: main.swift, other.swift",
801+
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
796802
"Starting Compiling main.swift, other.swift",
797803
"Finished Compiling main.swift, other.swift",
798804
"Starting Linking theModule",

0 commit comments

Comments
 (0)