Skip to content

Commit ea6158f

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 f2df547 commit ea6158f

File tree

8 files changed

+76
-435
lines changed

8 files changed

+76
-435
lines changed

Sources/SKCore/BuildSystemManager.swift

Lines changed: 13 additions & 151 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

@@ -201,11 +198,8 @@ extension BuildSystemManager {
201198
self.watchedFiles[uri] = (mainFile, language)
202199
}
203200

204-
let newStatus = await self.cachedStatusOrRegisterForSettings(for: mainFile, language: language)
205-
206-
if newStatus.buildSettingsChange != nil, let delegate = self._delegate {
207-
await delegate.fileBuildSettingsChanged([uri])
208-
}
201+
await buildSystem?.registerForChangeNotifications(for: mainFile, language: language)
202+
fallbackBuildSystem?.registerForChangeNotifications(for: mainFile, language: language)
209203
}
210204

211205
/// Return settings for `file` based on the `change` settings for `mainFile`.
@@ -226,115 +220,6 @@ extension BuildSystemManager {
226220
}
227221
}
228222

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

@@ -370,35 +254,22 @@ extension BuildSystemManager: BuildSystemDelegate {
370254
}
371255
}
372256

373-
public func fileBuildSettingsChangedImpl(_ changes: Set<DocumentURI>) async {
374-
var statusChanges: [DocumentURI: MainFileStatus] = [:]
375-
for mainFile in changes {
257+
public func fileBuildSettingsChangedImpl(_ changedFiles: Set<DocumentURI>) async {
258+
var changedWatchedFiles = Set<DocumentURI>()
259+
for mainFile in changedFiles {
376260
let watches = self.watchedFiles.filter { $1.mainFile == mainFile }
377-
guard let firstWatch = watches.first else {
261+
guard !watches.isEmpty else {
378262
// Possible notification after the file was unregistered. Ignore.
379263
continue
380264
}
381-
let newStatus: MainFileStatus
382-
383-
let settings = await self.buildSettings(for: mainFile, language: firstWatch.value.language)
384-
385-
if let settings {
386-
newStatus = settings.isFallback ? .fallback(settings.buildSettings) : .primary(settings.buildSettings)
387-
} else if let fallback = self.fallbackBuildSystem {
388-
// FIXME: we need to stop threading the language everywhere, or we need the build system
389-
// itself to pass it in here. Or alternatively cache the fallback settings/language earlier?
390-
let language = firstWatch.value.language
391-
if let settings = fallback.buildSettings(for: mainFile, language: language) {
392-
newStatus = .fallback(settings)
393-
} else {
394-
newStatus = .unsupported
395-
}
396-
} else {
397-
newStatus = .unsupported
265+
for watch in watches {
266+
changedWatchedFiles.insert(watch.key)
398267
}
399-
statusChanges[mainFile] = newStatus
400268
}
401-
await self.updateAndNotifyStatuses(changes: statusChanges)
269+
270+
if !changedWatchedFiles.isEmpty, let delegate = self._delegate {
271+
await delegate.fileBuildSettingsChanged(changedWatchedFiles)
272+
}
402273
}
403274

404275
// FIXME: (async) Make this method isolated once `BuildSystemDelegate` has ben asyncified
@@ -477,11 +348,7 @@ extension BuildSystemManager: MainFilesDelegate {
477348
log("main file for '\(uri)' changed old: '\(state.mainFile)' -> new: '\(newMainFile)'", level: .info)
478349
await self.checkUnreferencedMainFile(state.mainFile)
479350

480-
let newStatus = await self.cachedStatusOrRegisterForSettings(
481-
for: newMainFile, language: language)
482-
if newStatus.buildSettingsChange != nil {
483-
buildSettingsChanges.insert(uri)
484-
}
351+
buildSettingsChanges.insert(uri)
485352
}
486353
}
487354

@@ -497,11 +364,6 @@ extension BuildSystemManager {
497364
public func _cachedMainFile(for uri: DocumentURI) -> DocumentURI? {
498365
watchedFiles[uri]?.mainFile
499366
}
500-
501-
/// *For Testing* Returns the main file used for `uri`, if this is a registered file.
502-
public func _cachedMainFileSettings(for uri: DocumentURI) -> FileBuildSettings?? {
503-
mainFileStatuses[uri]?.buildSettings
504-
}
505367
}
506368

507369
/// 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
@@ -301,16 +301,7 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
301301

302302
public func registerForChangeNotifications(for uri: DocumentURI, language: Language) async {
303303
assert(self.watchedFiles[uri] == nil, "Registered twice for change notifications of the same URI")
304-
guard let delegate = self.delegate else { return }
305304
self.watchedFiles[uri] = language
306-
307-
var settings: FileBuildSettings? = nil
308-
do {
309-
settings = try self.buildSettings(for: uri, language: language)
310-
} catch {
311-
log("error computing settings: \(error)")
312-
}
313-
await delegate.fileBuildSettingsChanged([uri])
314305
}
315306

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

0 commit comments

Comments
 (0)