Skip to content

Commit e56c71f

Browse files
committed
Don’t run a swift build command to prepare a package manifest
Package manifests don’t have an associated target to prepare and are represented by a `ConfiguredTarget` with an empty target ID. We were mistakingly running `swift build` with an empty target name to prepare them, which failed. There is nothing to prepare.
1 parent 045024d commit e56c71f

File tree

8 files changed

+76
-19
lines changed

8 files changed

+76
-19
lines changed

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
550550
singleTarget target: ConfiguredTarget,
551551
indexProcessDidProduceResult: @Sendable (IndexProcessResult) -> Void
552552
) async throws {
553+
if target == .forPackageManifest {
554+
// Nothing to prepare for package manifests.
555+
return
556+
}
557+
553558
// TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target.
554559
// https://github.com/apple/sourcekit-lsp/issues/1254
555560
guard let toolchain = await toolchainRegistry.default else {

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ extension SourceKitLSPServer.Options {
2727
public static let testDefault = Self(swiftPublishDiagnosticsDebounceDuration: 0)
2828
}
2929

30+
fileprivate struct NotificationTimeoutError: Error, CustomStringConvertible {
31+
var description: String = "Failed to receive next notification within timeout"
32+
}
33+
3034
/// A mock SourceKit-LSP client (aka. a mock editor) that behaves like an editor
3135
/// for testing purposes.
3236
///
@@ -229,21 +233,17 @@ public final class TestSourceKitLSPClient: MessageHandler {
229233
///
230234
/// - Note: This also returns any notifications sent before the call to
231235
/// `nextNotification`.
232-
public func nextNotification(timeout: TimeInterval = defaultTimeout) async throws -> any NotificationType {
233-
struct TimeoutError: Error, CustomStringConvertible {
234-
var description: String = "Failed to receive next notification within timeout"
235-
}
236-
236+
public func nextNotification(timeout: Duration = .seconds(defaultTimeout)) async throws -> any NotificationType {
237237
return try await withThrowingTaskGroup(of: (any NotificationType).self) { taskGroup in
238238
taskGroup.addTask {
239239
for await notification in self.notifications {
240240
return notification
241241
}
242-
throw TimeoutError()
242+
throw NotificationTimeoutError()
243243
}
244244
taskGroup.addTask {
245-
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
246-
throw TimeoutError()
245+
try await Task.sleep(for: timeout)
246+
throw NotificationTimeoutError()
247247
}
248248
let result = try await taskGroup.next()!
249249
taskGroup.cancelAll()
@@ -256,7 +256,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
256256
/// If the next notification is not a `PublishDiagnosticsNotification`, this
257257
/// methods throws.
258258
public func nextDiagnosticsNotification(
259-
timeout: TimeInterval = defaultTimeout
259+
timeout: Duration = .seconds(defaultTimeout)
260260
) async throws -> PublishDiagnosticsNotification {
261261
guard !usePullDiagnostics else {
262262
struct PushDiagnosticsError: Error, CustomStringConvertible {
@@ -272,7 +272,7 @@ public final class TestSourceKitLSPClient: MessageHandler {
272272
public func nextNotification<ExpectedNotificationType: NotificationType>(
273273
ofType: ExpectedNotificationType.Type,
274274
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
275-
timeout: TimeInterval = defaultTimeout
275+
timeout: Duration = .seconds(defaultTimeout)
276276
) async throws -> ExpectedNotificationType {
277277
while true {
278278
let nextNotification = try await nextNotification(timeout: timeout)
@@ -282,6 +282,29 @@ public final class TestSourceKitLSPClient: MessageHandler {
282282
}
283283
}
284284

285+
/// Asserts that the test client does not receive a notification of the given type and satisfying the given predicate
286+
/// within the given duration.
287+
///
288+
/// For stable tests, the code that triggered the notification should be run before this assertion instead of relying
289+
/// on the duration.
290+
///
291+
/// The duration should not be 0 because we need to allow `nextNotification` some time to get the notification out of
292+
/// the `notifications` `AsyncStream`.
293+
public func assertDoesNotReceiveNotification<ExpectedNotificationType: NotificationType>(
294+
ofType: ExpectedNotificationType.Type,
295+
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
296+
within duration: Duration = .seconds(0.2)
297+
) async throws {
298+
do {
299+
let notification = try await nextNotification(
300+
ofType: ExpectedNotificationType.self,
301+
satisfying: predicate,
302+
timeout: duration
303+
)
304+
XCTFail("Did not expect to receive notification but received \(notification)")
305+
} catch is NotificationTimeoutError {}
306+
}
307+
285308
/// Handle the next request of the given type that is sent to the client.
286309
///
287310
/// The request handler will only handle a single request. If the request is called again, the request handler won't

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,24 +394,30 @@ public final actor SemanticIndexManager {
394394
let id = UUID()
395395
let task = Task(priority: priority) {
396396
await withLoggingScope("preparation") {
397+
defer {
398+
if inProgressPrepareForEditorTask?.id == id {
399+
inProgressPrepareForEditorTask = nil
400+
self.indexProgressStatusDidChange()
401+
}
402+
}
397403
// Should be kept in sync with `prepareFileForEditorFunctionality`
398404
guard let target = await buildSystemManager.canonicalConfiguredTarget(for: uri) else {
399405
return
400406
}
401407
if Task.isCancelled {
402408
return
403409
}
410+
if await preparationUpToDateTracker.isUpToDate(target) {
411+
// If the target is up-to-date, there is nothing to prepare.
412+
return
413+
}
404414
if inProgressPrepareForEditorTask?.id == id {
405415
if inProgressPrepareForEditorTask?.state != .determiningCanonicalConfiguredTarget {
406416
logger.fault("inProgressPrepareForEditorTask is in unexpected state")
407417
}
408418
inProgressPrepareForEditorTask?.state = .preparingTarget
409419
}
410420
await self.prepare(targets: [target], priority: nil)
411-
if inProgressPrepareForEditorTask?.id == id {
412-
inProgressPrepareForEditorTask = nil
413-
self.indexProgressStatusDidChange()
414-
}
415421
}
416422
}
417423
inProgressPrepareForEditorTask?.task.cancel()

Tests/SourceKitDTests/CrashRecoveryTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ final class CrashRecoveryTests: XCTestCase {
9090

9191
await swiftLanguageService._crash()
9292

93-
let crashedNotification = try await testClient.nextNotification(ofType: WorkDoneProgress.self, timeout: 5)
93+
let crashedNotification = try await testClient.nextNotification(ofType: WorkDoneProgress.self, timeout: .seconds(5))
9494
XCTAssertEqual(
9595
crashedNotification.value,
9696
.begin(
@@ -107,7 +107,7 @@ final class CrashRecoveryTests: XCTestCase {
107107
_ = try? await testClient.send(hoverRequest)
108108
let semanticFunctionalityRestoredNotification = try await testClient.nextNotification(
109109
ofType: WorkDoneProgress.self,
110-
timeout: 30
110+
timeout: .seconds(30)
111111
)
112112
XCTAssertEqual(semanticFunctionalityRestoredNotification.value, .end(WorkDoneProgressEnd()))
113113

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,4 +930,27 @@ final class BackgroundIndexingTests: XCTestCase {
930930
)
931931
XCTAssertEqual((result?.changes?.keys).map(Set.init), [uri, try project.uri(for: "Client.swift")])
932932
}
933+
934+
func testDontPreparePackageManifest() async throws {
935+
let project = try await SwiftPMTestProject(
936+
files: [
937+
"Lib.swift": ""
938+
],
939+
enableBackgroundIndexing: true
940+
)
941+
942+
_ = try await project.testClient.nextNotification(
943+
ofType: LogMessageNotification.self,
944+
satisfying: { $0.message.contains("Preparing MyLibrary") }
945+
)
946+
947+
// Opening the package manifest shouldn't cause any `swift build` calls to prepare them because they are not part of
948+
// a target that can be prepared.
949+
let (uri, _) = try project.openDocument("Package.swift")
950+
_ = try await project.testClient.send(DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri)))
951+
try await project.testClient.assertDoesNotReceiveNotification(
952+
ofType: LogMessageNotification.self,
953+
satisfying: { $0.message.contains("Preparing") }
954+
)
955+
}
933956
}

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ final class BuildSystemTests: XCTestCase {
193193

194194
var receivedCorrectDiagnostic = false
195195
for _ in 0..<Int(defaultTimeout) {
196-
let refreshedDiags = try await testClient.nextDiagnosticsNotification(timeout: 1)
196+
let refreshedDiags = try await testClient.nextDiagnosticsNotification(timeout: .seconds(1))
197197
if refreshedDiags.diagnostics.count == 0, try text == documentManager.latestSnapshot(doc).text {
198198
receivedCorrectDiagnostic = true
199199
break

Tests/SourceKitLSPTests/LocalSwiftTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,6 @@ final class LocalSwiftTests: XCTestCase {
14271427
XCTAssertEqual(diag.message, "Cannot find 'bar' in scope")
14281428

14291429
// Ensure that we don't get a second `PublishDiagnosticsNotification`
1430-
await assertThrowsError(try await testClient.nextDiagnosticsNotification(timeout: 2))
1430+
await assertThrowsError(try await testClient.nextDiagnosticsNotification(timeout: .seconds(2)))
14311431
}
14321432
}

Tests/SourceKitLSPTests/MainFilesProviderTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ final class MainFilesProviderTests: XCTestCase {
219219
// diagnostics.
220220
var receivedCorrectDiagnostic = false
221221
for _ in 0..<Int(defaultTimeout) {
222-
let refreshedDiags = try await project.testClient.nextDiagnosticsNotification(timeout: 1)
222+
let refreshedDiags = try await project.testClient.nextDiagnosticsNotification(timeout: .seconds(1))
223223
if let diagnostic = refreshedDiags.diagnostics.only,
224224
diagnostic.message == "Unused variable 'fromMyFancyLibrary'"
225225
{

0 commit comments

Comments
 (0)