@@ -134,11 +134,23 @@ extension IncrementalCompilationState.FirstWaveComputer {
134134 jobCreatingPch: jobCreatingPch)
135135
136136 // In the case where there are no compilation jobs to run on this build (no source-files were changed),
137- // we can skip running `beforeCompiles` jobs if we also ensure that none of the `afterCompiles` jobs
138- // have any dependencies on them.
139- let skipAllJobs = batchedCompilationJobs. isEmpty ? !nonVerifyAfterCompileJobsDependOnBeforeCompileJobs( ) : false
140- let beforeCompileJobs = skipAllJobs ? [ ] : jobsInPhases. beforeCompiles
141- var skippedNonCompileJobs = skipAllJobs ? jobsInPhases. beforeCompiles : [ ]
137+ // and the emit-module task does not need to be re-run, we can skip running `beforeCompiles` jobs if we
138+ // also ensure that none of the `afterCompiles` jobs have any dependencies on them.
139+ let skippingAllCompileJobs = batchedCompilationJobs. isEmpty
140+ let skipEmitModuleJobs = try skippingAllCompileJobs && computeCanSkipEmitModuleTasks ( buildRecord)
141+ let skipAllJobs = skippingAllCompileJobs && skipEmitModuleJobs && !nonVerifyAfterCompileJobsDependOnBeforeCompileJobs( )
142+
143+ let beforeCompileJobs : [ Job ]
144+ var skippedNonCompileJobs : [ Job ] = [ ]
145+ if skipAllJobs {
146+ beforeCompileJobs = [ ]
147+ skippedNonCompileJobs = jobsInPhases. beforeCompiles
148+ } else if skipEmitModuleJobs {
149+ beforeCompileJobs = jobsInPhases. beforeCompiles. filter { $0. kind != . emitModule }
150+ skippedNonCompileJobs. append ( contentsOf: jobsInPhases. beforeCompiles. filter { $0. kind == . emitModule } )
151+ } else {
152+ beforeCompileJobs = jobsInPhases. beforeCompiles
153+ }
142154
143155 // Schedule emitModule job together with verify module interface job.
144156 let afterCompileJobs = jobsInPhases. afterCompiles. compactMap { job -> Job ? in
@@ -170,6 +182,27 @@ extension IncrementalCompilationState.FirstWaveComputer {
170182 }
171183 }
172184
185+ /// Figure out if the emit-module tasks are *not* mandatory. This functionality only runs if there are not actual
186+ /// compilation tasks to be run in this build, for example on an emit-module-only build.
187+ private func computeCanSkipEmitModuleTasks( _ buildRecord: BuildRecord ) throws -> Bool {
188+ guard let emitModuleJob = jobsInPhases. beforeCompiles. first ( where: { $0. kind == . emitModule } ) else {
189+ return false // Nothing to skip, so no special handling is required
190+ }
191+ // If a non-emit-module task exists in 'beforeCompiles', it may be another kind of
192+ // changed dependency so we should re-run the module task as well
193+ guard jobsInPhases. beforeCompiles. allSatisfy ( { $0. kind == . emitModule } ) else {
194+ return false
195+ }
196+ // If any of the outputs do not exist, they must be re-computed
197+ guard try emitModuleJob. outputs. allSatisfy ( { try fileSystem. exists ( $0. file) } ) else {
198+ return false
199+ }
200+
201+ // Ensure that no output is older than any of the inputs
202+ let oldestOutputModTime : TimePoint = try emitModuleJob. outputs. map { try fileSystem. lastModificationTime ( for: $0. file) } . min ( ) ?? . distantPast
203+ return try emitModuleJob. inputs. swiftSourceFiles. allSatisfy ( { try fileSystem. lastModificationTime ( for: $0. typedFile. file) < oldestOutputModTime } )
204+ }
205+
173206 /// Figure out which compilation inputs are *not* mandatory at the start
174207 private func computeInitiallySkippedCompilationInputs(
175208 inputsInvalidatedByExternals: TransitivelyInvalidatedSwiftSourceFileSet ,
@@ -178,7 +211,7 @@ extension IncrementalCompilationState.FirstWaveComputer {
178211 ) -> Set < TypedVirtualPath > {
179212 let allCompileJobs = jobsInPhases. compileJobs
180213 // Input == source file
181- let changedInputs = computeChangedInputs ( moduleDependencyGraph , buildRecord)
214+ let changedInputs = computeChangedInputs ( buildRecord)
182215
183216 if let reporter = reporter {
184217 for input in inputsInvalidatedByExternals {
@@ -274,7 +307,6 @@ extension IncrementalCompilationState.FirstWaveComputer {
274307
275308 // Find the inputs that have changed since last compilation, or were marked as needed a build
276309 private func computeChangedInputs(
277- _ moduleDependencyGraph: ModuleDependencyGraph ,
278310 _ outOfDateBuildRecord: BuildRecord
279311 ) -> [ ChangedInput ] {
280312 jobsInPhases. compileJobs. compactMap { job in
0 commit comments