Skip to content

Commit c642b37

Browse files
committed
Remove tracking of file build settings status in SourceKitServer and BuildSystemManager
The core idea here is that the toolchain language servers always call into `BuildSystemManager` and `BuildSystemManager` will always reply with build settings. If it hasn’t computed them yet, it will reply with fallback settings. With that assumption, we can remove the `documentToPendingQueue` from `SourceKitServer` since there are no longer any documents that are pending – everything has a build settings immediately. Similarly, `BuildSystemManager.mainFileStatuses` also isn’t needed anymore. And lastly, since we know that `BuildSystemManager.buildSettings` will always return a value `registerForChangeNotifications` is changed not call `fileBuildSettingsChanged` immediately. Instead, it will only cause `fileBuildSettingsChanged` to be called when the file’s build settings change after the `registerForChangeNotifications` call.
1 parent dffcc93 commit c642b37

File tree

8 files changed

+73
-440
lines changed

8 files changed

+73
-440
lines changed

Sources/SKCore/BuildSystemManager.swift

Lines changed: 10 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,6 @@ public actor BuildSystemManager {
8989
/// The set of watched files, along with their main file and language.
9090
var watchedFiles: [DocumentURI: (mainFile: DocumentURI, language: Language)] = [:]
9191

92-
/// Statuses for each main file, containing build settings from the build systems.
93-
var mainFileStatuses: [DocumentURI: MainFileStatus] = [:]
94-
9592
/// The underlying primary build system.
9693
let buildSystem: BuildSystem?
9794

@@ -208,11 +205,8 @@ extension BuildSystemManager {
208205
self.watchedFiles[uri] = (mainFile, language)
209206
}
210207

211-
let newStatus = await self.cachedStatusOrRegisterForSettings(for: mainFile, language: language)
212-
213-
if newStatus.buildSettingsChange != nil, let delegate = self._delegate {
214-
await delegate.fileBuildSettingsChanged([uri])
215-
}
208+
await buildSystem?.registerForChangeNotifications(for: mainFile, language: language)
209+
fallbackBuildSystem?.registerForChangeNotifications(for: mainFile, language: language)
216210
}
217211

218212
/// Return settings for `file` based on the `change` settings for `mainFile`.
@@ -233,115 +227,6 @@ extension BuildSystemManager {
233227
}
234228
}
235229

236-
/// Handle a request for `FileBuildSettings` on `mainFile`.
237-
///
238-
/// Updates and returns the new `MainFileStatus` for `mainFile`.
239-
func cachedStatusOrRegisterForSettings(
240-
for mainFile: DocumentURI,
241-
language: Language
242-
) async -> MainFileStatus {
243-
// If we already have a status for the main file, use that.
244-
// Don't update any existing timeout.
245-
if let status = self.mainFileStatuses[mainFile] {
246-
return status
247-
}
248-
// This is a new `mainFile` that we need to handle. We need to fetch the
249-
// build settings.
250-
let newStatus: MainFileStatus
251-
if let buildSystem = self.buildSystem {
252-
// Register the timeout if it's applicable.
253-
if let fallback = self.fallbackBuildSystem {
254-
Task {
255-
try await Task.sleep(nanoseconds: UInt64(self.fallbackSettingsTimeout.nanoseconds()!))
256-
await self.handleFallbackTimer(for: mainFile, language: language, fallback)
257-
}
258-
}
259-
260-
// Intentionally register with the `BuildSystem` after setting the fallback to allow for
261-
// testing of the fallback system triggering before the `BuildSystem` can reply (e.g. if a
262-
// fallback time of 0 is specified).
263-
await buildSystem.registerForChangeNotifications(for: mainFile, language: language)
264-
265-
266-
newStatus = .waiting
267-
} else if let fallback = self.fallbackBuildSystem {
268-
// Only have a fallback build system. We consider it be a primary build
269-
// system that functions synchronously.
270-
if let settings = fallback.buildSettings(for: mainFile, language: language) {
271-
newStatus = .primary(settings)
272-
} else {
273-
newStatus = .unsupported
274-
}
275-
} else { // Don't have any build systems.
276-
newStatus = .unsupported
277-
}
278-
279-
if let status = self.mainFileStatuses[mainFile] {
280-
// Since we await above, another call to `cachedStatusOrRegisterForSettings`
281-
// might have set the main file status of `mainFile`. If this race happened,
282-
// return the value set by the concurrently executing function. This is safe
283-
// since all calls from this function are either side-effect free or
284-
// idempotent.
285-
return status
286-
}
287-
288-
self.mainFileStatuses[mainFile] = newStatus
289-
return newStatus
290-
}
291-
292-
/// Update and notify our delegate for the given main file changes if they are
293-
/// convertible into `FileBuildSettingsChange`.
294-
func updateAndNotifyStatuses(changes: [DocumentURI: MainFileStatus]) async {
295-
var changedWatchedFiles = Set<DocumentURI>()
296-
for (mainFile, status) in changes {
297-
let watches = self.watchedFiles.filter { $1.mainFile == mainFile }
298-
guard !watches.isEmpty else {
299-
// Possible notification after the file was unregistered. Ignore.
300-
continue
301-
}
302-
let prevStatus = self.mainFileStatuses[mainFile]
303-
self.mainFileStatuses[mainFile] = status
304-
305-
// It's possible that the command line arguments didn't change
306-
// (waitingFallback --> fallback), in that case we don't need to report a change.
307-
// If we were waiting though, we need to emit an initial change.
308-
guard prevStatus == .waiting || status.buildSettings != prevStatus?.buildSettings else {
309-
continue
310-
}
311-
guard status.buildSettingsChange != nil else {
312-
continue
313-
}
314-
for watch in watches {
315-
changedWatchedFiles.insert(watch.key)
316-
}
317-
}
318-
319-
if !changedWatchedFiles.isEmpty, let delegate = self._delegate {
320-
await delegate.fileBuildSettingsChanged(changedWatchedFiles)
321-
}
322-
}
323-
324-
/// Handle the fallback timer firing for a given `mainFile`.
325-
///
326-
/// Since this doesn't occur immediately it's possible that the `mainFile` is
327-
/// no longer referenced or is referenced by multiple watched files.
328-
func handleFallbackTimer(
329-
for mainFile: DocumentURI,
330-
language: Language,
331-
_ fallback: FallbackBuildSystem
332-
) async {
333-
// There won't be a current status if it's unreferenced by any watched file.
334-
// Similarly, if the status isn't `waiting` then there's nothing to do.
335-
guard let status = self.mainFileStatuses[mainFile], status == .waiting else {
336-
return
337-
}
338-
if let settings = fallback.buildSettings(for: mainFile, language: language) {
339-
await self.updateAndNotifyStatuses(changes: [mainFile: .waitingUsingFallback(settings)])
340-
} else {
341-
// Keep the status as waiting.
342-
}
343-
}
344-
345230
public func unregisterForChangeNotifications(for uri: DocumentURI) async {
346231
guard let mainFile = self.watchedFiles[uri]?.mainFile else {
347232
log("Unbalanced calls for registerForChangeNotifications and unregisterForChangeNotifications", level: .warning)
@@ -357,7 +242,6 @@ extension BuildSystemManager {
357242
if !self.watchedFiles.values.lazy.map({ $0.mainFile }).contains(mainFile) {
358243
// This was the last reference to the main file. Remove it.
359244
await self.buildSystem?.unregisterForChangeNotifications(for: mainFile)
360-
self.mainFileStatuses[mainFile] = nil
361245
}
362246
}
363247

@@ -377,35 +261,14 @@ extension BuildSystemManager: BuildSystemDelegate {
377261
}
378262
}
379263

380-
public func fileBuildSettingsChangedImpl(_ changes: Set<DocumentURI>) async {
381-
var statusChanges: [DocumentURI: MainFileStatus] = [:]
382-
for mainFile in changes {
383-
let watches = self.watchedFiles.filter { $1.mainFile == mainFile }
384-
guard let firstWatch = watches.first else {
385-
// Possible notification after the file was unregistered. Ignore.
386-
continue
387-
}
388-
let newStatus: MainFileStatus
389-
390-
let settings = await self.buildSettings(for: mainFile, language: firstWatch.value.language)
391-
392-
if let settings {
393-
newStatus = settings.isFallback ? .fallback(settings.buildSettings) : .primary(settings.buildSettings)
394-
} else if let fallback = self.fallbackBuildSystem {
395-
// FIXME: we need to stop threading the language everywhere, or we need the build system
396-
// itself to pass it in here. Or alternatively cache the fallback settings/language earlier?
397-
let language = firstWatch.value.language
398-
if let settings = fallback.buildSettings(for: mainFile, language: language) {
399-
newStatus = .fallback(settings)
400-
} else {
401-
newStatus = .unsupported
402-
}
403-
} else {
404-
newStatus = .unsupported
405-
}
406-
statusChanges[mainFile] = newStatus
264+
public func fileBuildSettingsChangedImpl(_ changedFiles: Set<DocumentURI>) async {
265+
let changedWatchedFiles = changedFiles.flatMap({ mainFile in
266+
self.watchedFiles.filter { $1.mainFile == mainFile }.keys
267+
})
268+
269+
if !changedWatchedFiles.isEmpty, let delegate = self._delegate {
270+
await delegate.fileBuildSettingsChanged(Set(changedWatchedFiles))
407271
}
408-
await self.updateAndNotifyStatuses(changes: statusChanges)
409272
}
410273

411274
// FIXME: (async) Make this method isolated once `BuildSystemDelegate` has ben asyncified
@@ -484,11 +347,7 @@ extension BuildSystemManager: MainFilesDelegate {
484347
log("main file for '\(uri)' changed old: '\(state.mainFile)' -> new: '\(newMainFile)'", level: .info)
485348
await self.checkUnreferencedMainFile(state.mainFile)
486349

487-
let newStatus = await self.cachedStatusOrRegisterForSettings(
488-
for: newMainFile, language: language)
489-
if newStatus.buildSettingsChange != nil {
490-
buildSettingsChanges.insert(uri)
491-
}
350+
buildSettingsChanges.insert(uri)
492351
}
493352
}
494353

@@ -504,11 +363,6 @@ extension BuildSystemManager {
504363
public func _cachedMainFile(for uri: DocumentURI) -> DocumentURI? {
505364
watchedFiles[uri]?.mainFile
506365
}
507-
508-
/// *For Testing* Returns the main file used for `uri`, if this is a registered file.
509-
public func _cachedMainFileSettings(for uri: DocumentURI) -> FileBuildSettings?? {
510-
mainFileStatuses[uri]?.buildSettings
511-
}
512366
}
513367

514368
/// Choose a new main file for the given uri, preferring to use a previous main file if still

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,6 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
9393

9494
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
9595
self.watchedFiles[uri] = language
96-
97-
guard let delegate = self.delegate else { return }
98-
99-
await delegate.fileBuildSettingsChanged([uri])
10096
}
10197

10298
/// We don't support change watching.

Sources/SKCore/FallbackBuildSystem.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ public final class FallbackBuildSystem: BuildSystem {
6060
}
6161

6262
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) {
63-
guard let delegate = self.delegate else { return }
64-
65-
Task {
66-
await delegate.fileBuildSettingsChanged([uri])
67-
}
63+
// Fallback build systems never change.
6864
}
6965

7066
/// We don't support change watching.

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,7 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
298298

299299
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
300300
assert(self.watchedFiles[uri] == nil, "Registered twice for change notifications of the same URI")
301-
guard let delegate = self.delegate else { return }
302301
self.watchedFiles[uri] = language
303-
304-
var settings: FileBuildSettings? = nil
305-
do {
306-
settings = try self.buildSettings(for: uri, language: language)
307-
} catch {
308-
log("error computing settings: \(error)")
309-
}
310-
await delegate.fileBuildSettingsChanged([uri])
311302
}
312303

313304
/// Unregister the given file for build-system level change notifications, such as command

0 commit comments

Comments
 (0)